// Set tile properties for a map tile, such as picture to use static void MapSetupTile(Map *map, const Vec2i pos, const Mission *m) { const int floor = m->FloorStyle % FLOOR_STYLE_COUNT; const int wall = m->WallStyle % WALL_STYLE_COUNT; const int room = m->RoomStyle % ROOM_STYLE_COUNT; Tile *tAbove = MapGetTile(map, Vec2iNew(pos.x, pos.y - 1)); bool canSeeTileAbove = !(tAbove != NULL && !TileCanSee(tAbove)); Tile *t = MapGetTile(map, pos); if (!t) { return; } switch (IMapGet(map, pos) & MAP_MASKACCESS) { case MAP_FLOOR: case MAP_SQUARE: t->pic = PicManagerGetMaskedStylePic( &gPicManager, "floor", floor, canSeeTileAbove ? FLOOR_NORMAL : FLOOR_SHADOW, m->FloorMask, m->AltMask); if (canSeeTileAbove) { // Normal floor tiles can be replaced randomly with // special floor tiles such as drainage t->flags |= MAPTILE_IS_NORMAL_FLOOR; } break; case MAP_ROOM: case MAP_DOOR: t->pic = PicManagerGetMaskedStylePic( &gPicManager, "room", room, canSeeTileAbove ? ROOMFLOOR_NORMAL : ROOMFLOOR_SHADOW, m->RoomMask, m->AltMask); break; case MAP_WALL: t->pic = PicManagerGetMaskedStylePic( &gPicManager, "wall", wall, MapGetWallPic(map, pos), m->WallMask, m->AltMask); t->flags = MAPTILE_NO_WALK | MAPTILE_NO_SHOOT | MAPTILE_NO_SEE | MAPTILE_IS_WALL; break; case MAP_NOTHING: t->pic = NULL; t->flags = MAPTILE_NO_WALK | MAPTILE_IS_NOTHING; break; } }
void CollideAllItems( const TTileItem *item, const Vec2i pos, const int mask, const CollisionTeam team, const bool isPVP, CollideItemFunc func, void *data) { const Vec2i tv = Vec2iToTile(pos); Vec2i dv; // Check collisions with all other items on this tile, in all 8 directions for (dv.y = -1; dv.y <= 1; dv.y++) { for (dv.x = -1; dv.x <= 1; dv.x++) { const Vec2i dtv = Vec2iAdd(tv, dv); if (!MapIsTileIn(&gMap, dtv)) { continue; } CArray *tileThings = &MapGetTile(&gMap, dtv)->things; for (int i = 0; i < (int)tileThings->size; i++) { TTileItem *ti = ThingIdGetTileItem(CArrayGet(tileThings, i)); // Don't collide if items are on the same team if (!CollisionIsOnSameTeam(ti, team, isPVP)) { if (item != ti && (ti->flags & mask) && ItemsCollide(item, ti, pos)) { func(ti, data); } } } } } }
static bool IsTileWalkable(Map *map, Vec2i pos) { if (!IsTileWalkableOrOpenable(map, pos)) { return false; } // Check if tile has a dangerous (explosive) item on it // For AI, we don't want to shoot it, so just walk around Tile *t = MapGetTile(map, pos); for (int i = 0; i < (int)t->things.size; i++) { ThingId *tid = CArrayGet(&t->things, i); // Only look for explosive objects if (tid->Kind != KIND_OBJECT) { continue; } TObject *o = CArrayGet(&gObjs, tid->Id); if (o->flags & OBJFLAG_DANGEROUS) { return false; } } return true; }
TTileItem *GetItemOnTileInCollision( TTileItem *item, Vec2i pos, int mask, CollisionTeam team, int isDogfight) { Vec2i tv = Vec2iToTile(pos); Vec2i dv; if (!MapIsTileIn(&gMap, tv)) { return NULL; } // Check collisions with all other items on this tile, in all 8 directions for (dv.y = -1; dv.y <= 1; dv.y++) { for (dv.x = -1; dv.x <= 1; dv.x++) { CArray *tileThings = &MapGetTile(&gMap, Vec2iAdd(tv, dv))->things; for (int i = 0; i < (int)tileThings->size; i++) { TTileItem *ti = ThingIdGetTileItem(CArrayGet(tileThings, i)); // Don't collide if items are on the same team if (!CollisionIsOnSameTeam(ti, team, isDogfight)) { if (item != ti && (ti->flags & mask) && ItemsCollide(item, ti, pos)) { return ti; } } } } } return NULL; }
static void ActionRun(Action *a, CArray *mapTriggers) { int i; switch (a->action) { case ACTION_NULL: return; case ACTION_SOUND: SoundPlayAt(&gSoundDevice, a->a.Sound, a->u.pos); break; case ACTION_SETTRIGGER: for (i = 0; i < (int)mapTriggers->size; i++) { Trigger *tr = *(Trigger **)CArrayGet(mapTriggers, i); if (tr->id == a->u.index) { tr->isActive = 1; break; } } break; case ACTION_CLEARTRIGGER: for (i = 0; i < (int)mapTriggers->size; i++) { Trigger *tr = *(Trigger **)CArrayGet(mapTriggers, i); if (tr->id == a->u.index) { tr->isActive = 0; break; } } break; case ACTION_CHANGETILE: { Tile *t= MapGetTile(&gMap, a->u.pos); t->flags = a->a.tileFlags; t->pic = a->tilePic; t->picAlt = a->tilePicAlt; } break; case ACTION_ACTIVATEWATCH: ActivateWatch(a->u.index); break; case ACTION_DEACTIVATEWATCH: DeactivateWatch(a->u.index); break; } }
void MapGenerateRandomExitArea(Map *map) { const Tile *t = NULL; for (int i = 0; i < 10000 && (t == NULL ||!TileCanWalk(t)); i++) { map->ExitStart.x = (rand() % (abs(map->Size.x) - EXIT_WIDTH - 1)); map->ExitEnd.x = map->ExitStart.x + EXIT_WIDTH + 1; map->ExitStart.y = (rand() % (abs(map->Size.y) - EXIT_HEIGHT - 1)); map->ExitEnd.y = map->ExitStart.y + EXIT_HEIGHT + 1; // Check that the exit area is walkable const Vec2i center = Vec2iNew( (map->ExitStart.x + map->ExitEnd.x) / 2, (map->ExitStart.y + map->ExitEnd.y) / 2); t = MapGetTile(map, center); } }
static void DrawObjectiveCompass( GraphicsDevice *g, Vec2i playerPos, Rect2i r, bool showExit) { // Draw exit position if (showExit) { DrawCompassArrow( g, r, MapGetExitPos(&gMap), playerPos, colorGreen, "Exit"); } // Draw objectives Map *map = &gMap; Vec2i tilePos; for (tilePos.y = 0; tilePos.y < map->Size.y; tilePos.y++) { for (tilePos.x = 0; tilePos.x < map->Size.x; tilePos.x++) { Tile *tile = MapGetTile(map, tilePos); for (int i = 0; i < (int)tile->things.size; i++) { TTileItem *ti = ThingIdGetTileItem(CArrayGet(&tile->things, i)); if (!(ti->flags & TILEITEM_OBJECTIVE)) { continue; } int objective = ObjectiveFromTileItem(ti->flags); MissionObjective *mo = CArrayGet(&gMission.missionData->Objectives, objective); if (mo->Flags & OBJECTIVE_HIDDEN) { continue; } if (!(mo->Flags & OBJECTIVE_POSKNOWN) && !tile->isVisited) { continue; } const ObjectiveDef *o = CArrayGet(&gMission.Objectives, objective); color_t color = o->color; DrawCompassArrow( g, r, Vec2iNew(ti->x, ti->y), playerPos, color, NULL); } } } }
void DrawBufferSetFromMap( DrawBuffer *buffer, Map *map, Vec2i origin, int width) { int x, y; Tile *bufTile; buffer->Size = Vec2iNew(width, buffer->OrigSize.y); buffer->xTop = origin.x - TILE_WIDTH * width / 2; //buffer->yTop = y_origin - 100; buffer->yTop = origin.y - TILE_HEIGHT * buffer->OrigSize.y / 2; buffer->xStart = buffer->xTop / TILE_WIDTH; buffer->yStart = buffer->yTop / TILE_HEIGHT; if (buffer->xTop < 0) { buffer->xStart--; } if (buffer->yTop < 0) { buffer->yStart--; } buffer->dx = buffer->xStart * TILE_WIDTH - buffer->xTop; buffer->dy = buffer->yStart * TILE_HEIGHT - buffer->yTop; bufTile = &buffer->tiles[0][0]; for (y = buffer->yStart; y < buffer->yStart + buffer->Size.y; y++) { for (x = buffer->xStart; x < buffer->xStart + buffer->Size.x; x++, bufTile++) { if (x >= 0 && x < map->Size.x && y >= 0 && y < map->Size.y) { *bufTile = *MapGetTile(map, Vec2iNew(x, y)); } else { *bufTile = TileNone(); } } bufTile += buffer->OrigSize.x - buffer->Size.x; } }
static int ConditionsMet(CArray *conditions) { int i; for (i = 0; i < (int)conditions->size; i++) { Condition *c = CArrayGet(conditions, i); switch (c->condition) { case CONDITION_TILECLEAR: if (!TileIsClear(MapGetTile(&gMap, c->pos))) { return 0; } break; } } return 1; }
static bool IsTileWalkableAroundObjects(Map *map, Vec2i pos) { if (!IsTileWalkableOrOpenable(map, pos)) { return false; } // Check if tile has any item on it Tile *t = MapGetTile(map, pos); for (int i = 0; i < (int)t->things.size; i++) { ThingId *tid = CArrayGet(&t->things, i); if (tid->Kind == KIND_OBJECT) { // Check that the object is not a pickup type TObject *o = CArrayGet(&gObjs, tid->Id); if (o->Type == OBJ_NONE && !(o->tileItem.flags & TILEITEM_IS_WRECK)) { return false; } } else if (tid->Kind == KIND_CHARACTER) { switch (gConfig.Game.AllyCollision) { case ALLYCOLLISION_NORMAL: return false; case ALLYCOLLISION_REPEL: // TODO: implement // Need to know collision team of player // to know if collision will result in repelling break; case ALLYCOLLISION_NONE: continue; default: CASSERT(false, "unknown collision type"); break; } } } return true; }
static bool IsTileWalkableOrOpenable(Map *map, Vec2i pos) { int tileFlags = MapGetTile(map, pos)->flags; if (!(tileFlags & MAPTILE_NO_WALK)) { return true; } if (tileFlags & MAPTILE_OFFSET_PIC) { // A door; check if we can open it int keycard = MapGetDoorKeycardFlag(map, pos); if (!keycard) { // Unlocked door return true; } return !!(keycard & gMission.flags); } // Otherwise, we cannot walk over this tile return false; }
static bool TryPlacePickup(HealthPickups *h) { Vec2i size = Vec2iNew(HEALTH_W, HEALTH_H); // Attempt to place one in unexplored area for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); if (!Vec2iIsZero(v) && !MapGetTile(h->map, Vec2iToTile(v))->isVisited) { MapPlaceHealth(v); return true; } } // Attempt to place one in out-of-sight area for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); const Vec2i fullpos = Vec2iReal2Full(v); const TActor *closestPlayer = AIGetClosestPlayer(fullpos); if (!Vec2iIsZero(v) && (!closestPlayer || CHEBYSHEV_DISTANCE( fullpos.x, fullpos.y, closestPlayer->Pos.x, closestPlayer->Pos.y) >= 256 * 150)) { MapPlaceHealth(v); return true; } } // Attempt to place one anyway for (int i = 0; i < 100; i++) { const Vec2i v = MapGenerateFreePosition(h->map, size); if (!Vec2iIsZero(v)) { MapPlaceHealth(v); return true; } } return false; }
void MapSetupTilesAndWalls(Map *map, const Mission *m) { // Pre-load the tile pics that this map will use // TODO: multiple styles and colours // Walls for (int i = 0; i < WALL_TYPES; i++) { PicManagerGenerateMaskedStylePic( &gPicManager, "wall", m->WallStyle, i, m->WallMask, m->AltMask); } // Floors for (int i = 0; i < FLOOR_TYPES; i++) { PicManagerGenerateMaskedStylePic( &gPicManager, "floor", m->FloorStyle, i, m->FloorMask, m->AltMask); } // Rooms for (int i = 0; i < ROOMFLOOR_TYPES; i++) { PicManagerGenerateMaskedStylePic( &gPicManager, "room", m->RoomStyle, i, m->RoomMask, m->AltMask); } Vec2i v; for (v.x = 0; v.x < map->Size.x; v.x++) { for (v.y = 0; v.y < map->Size.y; v.y++) { MapSetupTile(map, v, m); } } // Randomly change normal floor tiles to drainage tiles for (int i = 0; i < 50; i++) { // Make sure drain tiles aren't next to each other Tile *t = MapGetTile(map, Vec2iNew( (rand() % map->Size.x) & 0xFFFFFE, (rand() % map->Size.y) & 0xFFFFFE)); if (TileIsNormalFloor(t)) { TileSetAlternateFloor(t, PicManagerGetFromOld( &gPicManager, PIC_DRAINAGE)); t->flags |= MAPTILE_IS_DRAINAGE; } } int floor = m->FloorStyle % FLOOR_STYLE_COUNT; // Randomly change normal floor tiles to alternative floor tiles for (int i = 0; i < 100; i++) { Tile *t = MapGetTile( map, Vec2iNew(rand() % map->Size.x, rand() % map->Size.y)); if (TileIsNormalFloor(t)) { TileSetAlternateFloor(t, PicManagerGetMaskedStylePic( &gPicManager, "floor", floor, FLOOR_1, m->FloorMask, m->AltMask)); } } for (int i = 0; i < 150; i++) { Tile *t = MapGetTile( map, Vec2iNew(rand() % map->Size.x, rand() % map->Size.y)); if (TileIsNormalFloor(t)) { TileSetAlternateFloor(t, PicManagerGetMaskedStylePic( &gPicManager, "floor", floor, FLOOR_2, m->FloorMask, m->AltMask)); } } }
static void HandleGameEvent( GameEvent *e, HUD *hud, ScreenShake *shake, HealthPickups *hp, EventHandlers *eventHandlers) { switch (e->Type) { case GAME_EVENT_SCORE: Score(&gPlayerDatas[e->u.Score.PlayerIndex], e->u.Score.Score); HUDAddScoreUpdate(hud, e->u.Score.PlayerIndex, e->u.Score.Score); break; case GAME_EVENT_SOUND_AT: if (e->u.SoundAt.Sound) { SoundPlayAt( &gSoundDevice, e->u.SoundAt.Sound, e->u.SoundAt.Pos); } break; case GAME_EVENT_SCREEN_SHAKE: *shake = ScreenShakeAdd( *shake, e->u.ShakeAmount, gConfig.Graphics.ShakeMultiplier); break; case GAME_EVENT_SET_MESSAGE: HUDDisplayMessage( hud, e->u.SetMessage.Message, e->u.SetMessage.Ticks); break; case GAME_EVENT_GAME_START: if (eventHandlers->netInput.channel.state == CHANNEL_STATE_CONNECTED) { NetInputSendMsg( &eventHandlers->netInput, SERVER_MSG_GAME_START); } break; case GAME_EVENT_ADD_HEALTH_PICKUP: MapPlaceHealth(e->u.AddPos); break; case GAME_EVENT_TAKE_HEALTH_PICKUP: if (IsPlayerAlive(e->u.PickupPlayer)) { TActor *player = CArrayGet( &gActors, gPlayerIds[e->u.PickupPlayer]); ActorHeal(player, HEALTH_PICKUP_HEAL_AMOUNT); HealthPickupsRemoveOne(hp); HUDAddHealthUpdate( hud, e->u.PickupPlayer, HEALTH_PICKUP_HEAL_AMOUNT); } break; case GAME_EVENT_MOBILE_OBJECT_REMOVE: MobObjDestroy(e->u.MobileObjectRemoveId); break; case GAME_EVENT_ADD_BULLET: BulletAdd( e->u.AddBullet.Bullet, e->u.AddBullet.MuzzlePos, e->u.AddBullet.MuzzleHeight, e->u.AddBullet.Angle, e->u.AddBullet.Direction, e->u.AddBullet.Flags, e->u.AddBullet.PlayerIndex); break; case GAME_EVENT_ADD_MUZZLE_FLASH: AddMuzzleFlash( e->u.AddMuzzleFlash.FullPos, e->u.AddMuzzleFlash.MuzzleHeight, e->u.AddMuzzleFlash.Sprites, e->u.AddMuzzleFlash.Direction, e->u.AddMuzzleFlash.Color, e->u.AddMuzzleFlash.Duration); break; case GAME_EVENT_ADD_FIREBALL: AddFireball(e->u.AddFireball); break; case GAME_EVENT_HIT_CHARACTER: HitCharacter( e->u.HitCharacter.Flags, e->u.HitCharacter.PlayerIndex, CArrayGet(&gActors, e->u.HitCharacter.TargetId), e->u.HitCharacter.Special, e->u.HitCharacter.HasHitSound); break; case GAME_EVENT_ACTOR_IMPULSE: { TActor *a = CArrayGet(&gActors, e->u.ActorImpulse.Id); a->Vel = Vec2iAdd(a->Vel, e->u.ActorImpulse.Vel); } break; case GAME_EVENT_DAMAGE_CHARACTER: DamageCharacter( e->u.DamageCharacter.Power, e->u.DamageCharacter.PlayerIndex, CArrayGet(&gActors, e->u.DamageCharacter.TargetId)); if (e->u.DamageCharacter.Power != 0) { HUDAddHealthUpdate( hud, e->u.DamageCharacter.TargetPlayerIndex, -e->u.DamageCharacter.Power); } break; case GAME_EVENT_TRIGGER: { const Tile *t = MapGetTile(&gMap, e->u.Trigger.TilePos); for (int i = 0; i < (int)t->triggers.size; i++) { Trigger **tp = CArrayGet(&t->triggers, i); if ((*tp)->id == e->u.Trigger.Id) { TriggerActivate(*tp, &gMap.triggers); break; } } } break; case GAME_EVENT_UPDATE_OBJECTIVE: { struct Objective *o = CArrayGet( &gMission.Objectives, e->u.UpdateObjective.ObjectiveIndex); o->done += e->u.UpdateObjective.Update; MissionObjective *mo = CArrayGet( &gMission.missionData->Objectives, e->u.UpdateObjective.ObjectiveIndex); switch (mo->Type) { case OBJECTIVE_COLLECT: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = PICKUP_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; case OBJECTIVE_DESTROY: { GameEvent e1; e1.Type = GAME_EVENT_SCORE; e1.u.Score.PlayerIndex = e->u.UpdateObjective.PlayerIndex; e1.u.Score.Score = OBJECT_SCORE; HandleGameEvent(&e1, hud, shake, hp, eventHandlers); } break; default: // No other special objective handling break; } // Display a text update effect for the objective HUDAddObjectiveUpdate( hud, e->u.UpdateObjective.ObjectiveIndex, e->u.UpdateObjective.Update); MissionSetMessageIfComplete(&gMission); } break; case GAME_EVENT_MISSION_COMPLETE: HUDDisplayMessage(hud, "Mission complete", -1); hud->showExit = true; MapShowExitArea(&gMap); break; case GAME_EVENT_MISSION_INCOMPLETE: gMission.state = MISSION_STATE_PLAY; break; case GAME_EVENT_MISSION_PICKUP: gMission.state = MISSION_STATE_PICKUP; gMission.pickupTime = gMission.time; SoundPlay(&gSoundDevice, StrSound("whistle")); break; case GAME_EVENT_MISSION_END: gMission.isDone = true; break; default: assert(0 && "unknown game event"); break; } }
static bool IsPosNoSee(void *data, Vec2i pos) { const Tile *t = MapGetTile(data, Vec2iToTile(pos)); return t != NULL && (t->flags & MAPTILE_NO_SEE); }
/* * Pathfind takes ownership of _Path and then releases ownership of _Path when it calls _Path's callback. */ int PathfindNext(struct PathData* _Path, void* _None) { const struct Tile* _StartTile = MapGetTile(g_GameWorld.MapRenderer, &_Path->Start); const struct Tile* _Goal = MapGetTile(g_GameWorld.MapRenderer, &_Path->End); struct Tile* _Neighbors[TILE_SIZE]; /* * TODO: Remove LinkedList from PathfindNext to remove malloc calls from PathfindNext. */ struct LinkedList _ClosedList = {0, NULL, NULL}; struct LnkLst_Node* _Itr = NULL; struct BinaryHeap _OpenList = {NULL, PATHFIND_OPENSIZE, 0, PathNodeScoreCmp}; struct PathNodeScore* _Current = NULL; _OpenList.Table = PathStackNext(); BinaryHeapInsert(&_OpenList, CreatePathNodeScore(_StartTile, 0, _Path->Heuristic(_StartTile, _Goal), TILE_SIZE, NULL)); while(_OpenList.Size > 0 && _OpenList.Size <= _OpenList.TblSz) { _Current = BinaryHeapTop(&_OpenList); LnkLstPushBack(&_ClosedList, BinaryHeapPop(&_OpenList)); if(_Current->Node->TilePos.x == _Goal->TilePos.x && _Current->Node->TilePos.y == _Goal->TilePos.y) break; /* * Fill the open list with _Tile's neighbors. */ TileGetAdjTiles(g_GameWorld.MapRenderer, _Current->Node, _Neighbors); for(int i = 0; i < TILE_SIZE; ++i) { if(_Neighbors[i] != NULL) { const struct LnkLst_Node* CloseItr = _ClosedList.Front; while(CloseItr != NULL) { const struct PathNodeScore* _Node = (struct PathNodeScore*)CloseItr->Data; if(_Node->Node == _Neighbors[i]) { goto loop_end; } CloseItr = CloseItr->Next; } /* * Check if neighbors are already in open list. */ for(int j = 0; j < _OpenList.Size; ++j) { struct PathNodeScore* _OpenTemp = (struct PathNodeScore*)_OpenList.Table[j]; if(_OpenTemp->Node == _Neighbors[i]) { int _gCost = _Current->g + _Path->Heuristic(_OpenTemp->Node, _Neighbors[i]); if(_gCost < _OpenTemp->g) { _OpenTemp->g = _gCost; BinaryHeapIncrease(&_OpenList, j); } goto loop_end; } } BinaryHeapInsert(&_OpenList, CreatePathNodeScore(_Neighbors[i], _Current->g + 1, _Path->Heuristic(_Neighbors[i], _Goal), i, _Current)); loop_end: continue; } } } while(_OpenList.Size > 0) { DestroyPathNodeScore((struct PathNodeScore*)BinaryHeapPop(&_OpenList)); } PathStackFree(_OpenList.Table); _Itr = _ClosedList.Front; //ListToPath(&_ClosedList, _Path->Path); const struct PathNodeScore* _Temp = _Current; struct LinkedList _TempList = {0, NULL, NULL}; while(_Temp != NULL) { LnkLstPushFront(&_TempList, (struct PathNodeScore*)_Temp); _Temp = _Temp->Parent; } ListToPath(&_TempList, _Path->Path); while(_Itr != NULL) { DestroyPathNodeScore((struct PathNodeScore*)_Itr->Data); _Itr = _Itr->Next; } LnkLstClear(&_ClosedList); _Path->Callback(_Path->Data, _Path->Path); MemPoolFree(g_PathDataPool, _Path); return 0; }
static bool IsPosNoSee(void *data, Vec2i pos) { return MapGetTile(data, Vec2iToTile(pos))->flags & MAPTILE_NO_SEE; }
bool UpdateBullet(TMobileObject *obj, const int ticks) { obj->count += ticks; obj->soundLock = MAX(0, obj->soundLock - ticks); obj->specialLock = MAX(0, obj->specialLock - ticks); if (obj->count < obj->bulletClass->Delay) { return true; } if (obj->range >= 0 && obj->count > obj->range) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->OutOfRangeGuns); } return false; } const Vec2i objPos = Vec2iNew(obj->x, obj->y); if (obj->bulletClass->SeekFactor > 0) { // Find the closest target to this bullet and steer towards it const TActor *owner = ActorGetByUID(obj->ActorUID); if (owner == NULL) { return false; } const TActor *target = AIGetClosestEnemy(objPos, owner, obj->flags); if (target && !target->dead) { for (int i = 0; i < ticks; i++) { obj->vel = SeekTowards( objPos, obj->vel, obj->bulletClass->SpeedLow, target->Pos, obj->bulletClass->SeekFactor); } } } Vec2i pos = Vec2iScale(Vec2iAdd(objPos, obj->vel), ticks); HitType hitItem = HIT_NONE; if (!gCampaign.IsClient) { hitItem = HitItem(obj, pos, obj->bulletClass->Persists); } const Vec2i realPos = Vec2iFull2Real(pos); // Falling (grenades) if (obj->bulletClass->Falling.GravityFactor != 0) { bool hasDropped = obj->z <= 0; for (int i = 0; i < ticks; i++) { obj->z += obj->dz; if (obj->z <= 0) { obj->z = 0; if (obj->bulletClass->Falling.Bounces) { obj->dz = -obj->dz / 2; } else { obj->dz = 0; } if (!hasDropped) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->Falling.DropGuns); } } hasDropped = true; if (obj->bulletClass->Falling.DestroyOnDrop) { return false; } SoundPlayAt( &gSoundDevice, StrSound(obj->bulletClass->HitSound.Wall), realPos); } else { obj->dz -= obj->bulletClass->Falling.GravityFactor; } if (!obj->bulletClass->Falling.FallsDown) { obj->dz = MAX(0, obj->dz); } } } // Friction const bool isDiagonal = obj->vel.x != 0 && obj->vel.y != 0; int frictionComponent = isDiagonal ? (int)round(obj->bulletClass->Friction / sqrt(2)) : obj->bulletClass->Friction; for (int i = 0; i < ticks; i++) { if (obj->vel.x > 0) { obj->vel.x -= frictionComponent; } else if (obj->vel.x < 0) { obj->vel.x += frictionComponent; } if (obj->vel.y > 0) { obj->vel.y -= frictionComponent; } else if (obj->vel.y < 0) { obj->vel.y += frictionComponent; } } bool hitWall = false; if (!gCampaign.IsClient && hitItem == HIT_NONE) { hitWall = MapIsRealPosIn(&gMap, realPos) && ShootWall(realPos.x, realPos.y); } if (hitWall || hitItem != HIT_NONE) { GameEvent b = GameEventNew(GAME_EVENT_BULLET_BOUNCE); b.u.BulletBounce.UID = obj->UID; if (hitWall && !Vec2iIsZero(obj->vel)) { b.u.BulletBounce.HitType = (int)HIT_WALL; } else { b.u.BulletBounce.HitType = (int)hitItem; } bool alive = true; if ((hitWall && !obj->bulletClass->WallBounces) || ((hitItem != HIT_NONE) && obj->bulletClass->HitsObjects)) { b.u.BulletBounce.Spark = true; CASSERT(!gCampaign.IsClient, "Cannot process bounces as client"); FireGuns(obj, &obj->bulletClass->HitGuns); if (hitWall || !obj->bulletClass->Persists) { alive = false; } } b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(obj->vel); if (hitWall && !Vec2iIsZero(obj->vel)) { // Bouncing Vec2i bounceVel = obj->vel; pos = GetWallBounceFullPos(objPos, pos, &bounceVel); b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(bounceVel); obj->vel = bounceVel; } GameEventsEnqueue(&gGameEvents, b); if (!alive) { return false; } } if (!MapTryMoveTileItem(&gMap, &obj->tileItem, realPos)) { obj->count = obj->range; return false; } obj->x = pos.x; obj->y = pos.y; if (obj->bulletClass->Erratic) { for (int i = 0; i < ticks; i++) { obj->vel.x += ((rand() % 3) - 1) * 128; obj->vel.y += ((rand() % 3) - 1) * 128; } } // Proximity function, destroy // Only check proximity every now and then if (obj->bulletClass->ProximityGuns.size > 0 && !(obj->count & 3)) { if (!gCampaign.IsClient) { // Detonate the mine if there are characters in the tiles around it const Vec2i tv = Vec2iToTile(Vec2iFull2Real(pos)); Vec2i dv; for (dv.y = -1; dv.y <= 1; dv.y++) { for (dv.x = -1; dv.x <= 1; dv.x++) { const Vec2i dtv = Vec2iAdd(tv, dv); if (!MapIsTileIn(&gMap, dtv)) { continue; } if (TileHasCharacter(MapGetTile(&gMap, dtv))) { FireGuns(obj, &obj->bulletClass->ProximityGuns); return false; } } } } } return true; }
//update homie PUBLIC RETCODE HomieUpdate(hHOMIE homie, hGMAP map) { int lastIndX = homie->indX; int lastIndY = homie->indY; if((TESTFLAGS(homie->status, HOMIE_MOVING) || TESTFLAGS(homie->status, HOMIE_STUCK)) && !TESTFLAGS(homie->status, HOMIE_DEAD)) { float orient[eMaxPt]; OBJGetOrientation(homie->obj, orient); orient[eX] *= -1; int nextIndX = homie->indX + (orient[eX] <= 0 ? (orient[eX] < 0 ? -1 : 0) : 1); int nextIndY = homie->indY + (orient[eZ] <= 0 ? (orient[eZ] < 0 ? -1 : 0) : 1); hTILE nextTile; //check to see if next ind is out of bound if(nextIndX < 0 || nextIndX >= map->indSizeX || nextIndY < 0 || nextIndY >= map->indSizeY) return HomieStop(homie, map); //get next tile nextTile = &CELL(map->tiles, nextIndY, nextIndX, map->indSizeX); assert(nextTile); //check if occupied if(TESTFLAGS(nextTile->status, TILE_OCCUPIED)) return HomieStop(homie, map); //check if next tile is a wall or etc... switch(nextTile->type) { case eTILE_WALL: return HomieStop(homie, map); case eTILE_BOUNCE: //inverse direction orient[eZ] *= -1; OBJSetOrientation(homie->obj, orient[eX],orient[eY],orient[eZ]); homie->indX = nextIndX; homie->indY = nextIndY; return RETCODE_SUCCESS; case eTILE_DOOR: if(nextTile->homieType == homie->type) //destroy this door (change to floor) { float tileLoc[eMaxPt]; OBJGetLoc(nextTile->obj, tileLoc); float floorLoc[eMaxPt]; OBJDestroy(&nextTile->obj); //look for a floor tile hTILE theTile = MapGetTile(map, eTILE_FLOOR); if(theTile) { OBJGetLoc(theTile->obj, floorLoc); nextTile->tileID = theTile->tileID; nextTile->obj = OBJDuplicate(theTile->obj, OBJGetID(theTile->obj), tileLoc[eX], floorLoc[eY], tileLoc[eZ]); } else { OBJGetLoc(map->floor.obj, floorLoc); nextTile->tileID = map->floor.tileID; nextTile->obj = OBJDuplicate(map->floor.obj, OBJGetID(map->floor.obj), tileLoc[eX], floorLoc[eY], tileLoc[eZ]); } nextTile->homieType = map->floor.homieType; nextTile->status = map->floor.status; nextTile->type = map->floor.type; } else return HomieStop(homie, map); break; } //otherwise, move homie! OBJMovLoc(homie->obj, orient[eX], 0, orient[eZ]); if(TESTFLAGS(homie->status, HOMIE_STUCK)) { SETFLAG(homie->status, HOMIE_MOVING); CLEARFLAG(homie->status, HOMIE_STUCK); hTILE tile = &CELL(map->tiles, homie->indY, homie->indX, map->indSizeX); CLEARFLAG(tile->status, TILE_OCCUPIED); } float homieLoc[eMaxPt]; OBJGetLoc(homie->obj, homieLoc); float tileLoc[eMaxPt]; OBJGetLoc(nextTile->obj, tileLoc); //check to see if homie has reached the next tile if((orient[eX] > 0 && homieLoc[eX] >= tileLoc[eX]) || (orient[eX] < 0 && homieLoc[eX] <= tileLoc[eX]) || (orient[eZ] > 0 && homieLoc[eZ] >= tileLoc[eZ]) || (orient[eZ] < 0 && homieLoc[eZ] <= tileLoc[eZ])) { homie->indX = nextIndX; homie->indY = nextIndY; //determine course of action with specific tile if(nextTile->homieType == eHOMIE_NOCOLOR || nextTile->homieType == homie->type) { switch(nextTile->type) { case eTILE_ARROW_N: OBJSetLoc(homie->obj, tileLoc[eX], homieLoc[eY], tileLoc[eZ]); OBJSetOrientation(homie->obj, 0, 0, -GCFGGetHomieSpd()); return RETCODE_SUCCESS; case eTILE_ARROW_S: OBJSetLoc(homie->obj, tileLoc[eX], homieLoc[eY], tileLoc[eZ]); OBJSetOrientation(homie->obj, 0, 0, GCFGGetHomieSpd()); return RETCODE_SUCCESS; case eTILE_ARROW_E: OBJSetLoc(homie->obj, tileLoc[eX], homieLoc[eY], tileLoc[eZ]); OBJSetOrientation(homie->obj, -GCFGGetHomieSpd(), 0, 0); return RETCODE_SUCCESS; case eTILE_ARROW_W: OBJSetLoc(homie->obj, tileLoc[eX], homieLoc[eY], tileLoc[eZ]); OBJSetOrientation(homie->obj, GCFGGetHomieSpd(), 0, 0); return RETCODE_SUCCESS; case eTILE_TELEPORT: { hTILE theTile, lastTile; //look for another teleport tile similar to this for(int row = 0; row < map->indSizeY; row++) { for(int col = 0; col < map->indSizeX; col++) { theTile = &CELL(map->tiles, row, col, map->indSizeX); if((col != nextIndX || row != nextIndY) && !TESTFLAGS(theTile->status, TILE_OCCUPIED) && theTile->type == eTILE_TELEPORT && (theTile->homieType == nextTile->homieType)) { //teleport homie here OBJGetLoc(theTile->obj, tileLoc); OBJSetLoc(homie->obj, tileLoc[eX], homieLoc[eY], tileLoc[eZ]); lastTile = &CELL(map->tiles, lastIndY, lastIndX, map->indSizeX); CLEARFLAG(lastTile->status, TILE_OCCUPIED); homie->indX = col; homie->indY = row; return RETCODE_SUCCESS; } } } } return RETCODE_SUCCESS; case eTILE_HOLE: { CLEARFLAG(homie->status, HOMIE_MOVING); SETFLAG(homie->status, HOMIE_DEAD); float tileLoc[eMaxPt]; OBJGetLoc(nextTile->obj, tileLoc); float floorLoc[eMaxPt]; OBJDestroy(&nextTile->obj); //look for a floor tile hTILE theTile = MapGetTile(map, eTILE_FLOOR); if(theTile) { OBJGetLoc(theTile->obj, floorLoc); nextTile->tileID = theTile->tileID; nextTile->obj = OBJDuplicate(theTile->obj, OBJGetID(theTile->obj), tileLoc[eX], floorLoc[eY], tileLoc[eZ]); } else { OBJGetLoc(map->floor.obj, floorLoc); nextTile->tileID = map->floor.tileID; nextTile->obj = OBJDuplicate(map->floor.obj, OBJGetID(map->floor.obj), tileLoc[eX], floorLoc[eY], tileLoc[eZ]); } nextTile->homieType = map->floor.homieType; nextTile->status = map->floor.status; nextTile->type = map->floor.type; } return RETCODE_SUCCESS; } } } } return RETCODE_SUCCESS; }
static void HandleGameEvent( const GameEvent e, Camera *camera, PowerupSpawner *healthSpawner, CArray *ammoSpawners) { switch (e.Type) { case GAME_EVENT_PLAYER_DATA: PlayerDataAddOrUpdate(e.u.PlayerData); break; case GAME_EVENT_TILE_SET: { Tile *t = MapGetTile(&gMap, Net2Vec2i(e.u.TileSet.Pos)); t->flags = e.u.TileSet.Flags; t->pic = PicManagerGetNamedPic( &gPicManager, e.u.TileSet.PicName); t->picAlt = PicManagerGetNamedPic( &gPicManager, e.u.TileSet.PicAltName); } break; case GAME_EVENT_MAP_OBJECT_ADD: ObjAdd(e.u.MapObjectAdd); break; case GAME_EVENT_MAP_OBJECT_DAMAGE: DamageObject(e.u.MapObjectDamage); break; case GAME_EVENT_SCORE: { PlayerData *p = PlayerDataGetByUID(e.u.Score.PlayerUID); PlayerScore(p, e.u.Score.Score); HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_SCORE, e.u.Score.PlayerUID, e.u.Score.Score); } break; case GAME_EVENT_SOUND_AT: if (!e.u.SoundAt.IsHit || ConfigGetBool(&gConfig, "Sound.Hits")) { SoundPlayAt( &gSoundDevice, StrSound(e.u.SoundAt.Sound), Net2Vec2i(e.u.SoundAt.Pos)); } break; case GAME_EVENT_SCREEN_SHAKE: camera->shake = ScreenShakeAdd( camera->shake, e.u.ShakeAmount, ConfigGetInt(&gConfig, "Graphics.ShakeMultiplier")); break; case GAME_EVENT_SET_MESSAGE: HUDDisplayMessage( &camera->HUD, e.u.SetMessage.Message, e.u.SetMessage.Ticks); break; case GAME_EVENT_GAME_START: gMission.HasStarted = true; break; case GAME_EVENT_ACTOR_ADD: ActorAdd(e.u.ActorAdd); break; case GAME_EVENT_ACTOR_MOVE: ActorMove(e.u.ActorMove); break; case GAME_EVENT_ACTOR_STATE: { TActor *a = ActorGetByUID(e.u.ActorState.UID); if (!a->isInUse) break; ActorSetState(a, (ActorAnimation)e.u.ActorState.State); } break; case GAME_EVENT_ACTOR_DIR: { TActor *a = ActorGetByUID(e.u.ActorDir.UID); if (!a->isInUse) break; a->direction = (direction_e)e.u.ActorDir.Dir; } break; case GAME_EVENT_ACTOR_SLIDE: { TActor *a = ActorGetByUID(e.u.ActorSlide.UID); if (!a->isInUse) break; a->Vel = Net2Vec2i(e.u.ActorSlide.Vel); // Slide sound if (ConfigGetBool(&gConfig, "Sound.Footsteps")) { SoundPlayAt( &gSoundDevice, gSoundDevice.slideSound, Vec2iNew(a->tileItem.x, a->tileItem.y)); } } break; case GAME_EVENT_ACTOR_IMPULSE: { TActor *a = ActorGetByUID(e.u.ActorImpulse.UID); if (!a->isInUse) break; a->Vel = Vec2iAdd(a->Vel, Net2Vec2i(e.u.ActorImpulse.Vel)); const Vec2i pos = Net2Vec2i(e.u.ActorImpulse.Pos); if (!Vec2iIsZero(pos)) { a->Pos = pos; } } break; case GAME_EVENT_ACTOR_SWITCH_GUN: ActorSwitchGun(e.u.ActorSwitchGun); break; case GAME_EVENT_ACTOR_PICKUP_ALL: { TActor *a = ActorGetByUID(e.u.ActorPickupAll.UID); if (!a->isInUse) break; a->PickupAll = e.u.ActorPickupAll.PickupAll; } break; case GAME_EVENT_ACTOR_REPLACE_GUN: ActorReplaceGun(e.u.ActorReplaceGun); break; case GAME_EVENT_ACTOR_HEAL: { TActor *a = ActorGetByUID(e.u.Heal.UID); if (!a->isInUse || a->dead) break; ActorHeal(a, e.u.Heal.Amount); // Sound of healing SoundPlayAt( &gSoundDevice, gSoundDevice.healthSound, Vec2iFull2Real(a->Pos)); // Tell the spawner that we took a health so we can // spawn more (but only if we're the server) if (e.u.Heal.IsRandomSpawned && !gCampaign.IsClient) { PowerupSpawnerRemoveOne(healthSpawner); } if (e.u.Heal.PlayerUID >= 0) { HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_HEALTH, e.u.Heal.PlayerUID, e.u.Heal.Amount); } } break; case GAME_EVENT_ACTOR_ADD_AMMO: { TActor *a = ActorGetByUID(e.u.AddAmmo.UID); if (!a->isInUse || a->dead) break; ActorAddAmmo(a, e.u.AddAmmo.AmmoId, e.u.AddAmmo.Amount); // Tell the spawner that we took ammo so we can // spawn more (but only if we're the server) if (e.u.AddAmmo.IsRandomSpawned && !gCampaign.IsClient) { PowerupSpawnerRemoveOne( CArrayGet(ammoSpawners, e.u.AddAmmo.AmmoId)); } if (e.u.AddAmmo.PlayerUID >= 0) { HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_AMMO, e.u.AddAmmo.PlayerUID, e.u.AddAmmo.Amount); } } break; case GAME_EVENT_ACTOR_USE_AMMO: { TActor *a = ActorGetByUID(e.u.UseAmmo.UID); if (!a->isInUse || a->dead) break; ActorAddAmmo(a, e.u.UseAmmo.AmmoId, -(int)e.u.UseAmmo.Amount); if (e.u.UseAmmo.PlayerUID >= 0) { HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_AMMO, e.u.UseAmmo.PlayerUID, -(int)e.u.UseAmmo.Amount); } } break; case GAME_EVENT_ACTOR_DIE: { TActor *a = ActorGetByUID(e.u.ActorDie.UID); // Check if the player has lives to revive PlayerData *p = PlayerDataGetByUID(a->PlayerUID); if (p != NULL) { p->Lives--; CASSERT(p->Lives >= 0, "Player has died too many times"); if (p->Lives > 0 && !gCampaign.IsClient) { // Find the closest player alive; try to spawn next to that position // if no other suitable position exists Vec2i defaultSpawnPosition = Vec2iZero(); const TActor *closestActor = AIGetClosestPlayer(a->Pos); if (closestActor != NULL) defaultSpawnPosition = closestActor->Pos; PlacePlayer(&gMap, p, defaultSpawnPosition, false); } } ActorDestroy(a); } break; case GAME_EVENT_ACTOR_MELEE: { const TActor *a = ActorGetByUID(e.u.Melee.UID); if (!a->isInUse) break; const BulletClass *b = StrBulletClass(e.u.Melee.BulletClass); if ((HitType)e.u.Melee.HitType != HIT_NONE && HasHitSound(b->Power, a->flags, a->PlayerUID, (TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID, SPECIAL_NONE, false)) { PlayHitSound( &b->HitSound, (HitType)e.u.Melee.HitType, Vec2iFull2Real(a->Pos)); } if (!gCampaign.IsClient) { Damage( Vec2iZero(), b->Power, a->flags, a->PlayerUID, a->uid, (TileItemKind)e.u.Melee.TargetKind, e.u.Melee.TargetUID, SPECIAL_NONE); } } break; case GAME_EVENT_ADD_PICKUP: PickupAdd(e.u.AddPickup); // Play a spawn sound SoundPlayAt( &gSoundDevice, StrSound("spawn_item"), Net2Vec2i(e.u.AddPickup.Pos)); break; case GAME_EVENT_REMOVE_PICKUP: PickupDestroy(e.u.RemovePickup.UID); if (e.u.RemovePickup.SpawnerUID >= 0) { TObject *o = ObjGetByUID(e.u.RemovePickup.SpawnerUID); o->counter = AMMO_SPAWNER_RESPAWN_TICKS; } break; case GAME_EVENT_BULLET_BOUNCE: { TMobileObject *o = MobObjGetByUID(e.u.BulletBounce.UID); if (o == NULL || !o->isInUse) break; const Vec2i pos = Net2Vec2i(e.u.BulletBounce.BouncePos); PlayHitSound( &o->bulletClass->HitSound, (HitType)e.u.BulletBounce.HitType, Vec2iFull2Real(pos)); if (e.u.BulletBounce.Spark && o->bulletClass->Spark != NULL) { GameEvent s = GameEventNew(GAME_EVENT_ADD_PARTICLE); s.u.AddParticle.Class = o->bulletClass->Spark; s.u.AddParticle.FullPos = pos; s.u.AddParticle.Z = o->z; GameEventsEnqueue(&gGameEvents, s); } o->x = pos.x; o->y = pos.y; o->vel = Net2Vec2i(e.u.BulletBounce.BounceVel); } break; case GAME_EVENT_REMOVE_BULLET: { TMobileObject *o = MobObjGetByUID(e.u.RemoveBullet.UID); if (o == NULL || !o->isInUse) break; MobObjDestroy(o); } break; case GAME_EVENT_PARTICLE_REMOVE: ParticleDestroy(&gParticles, e.u.ParticleRemoveId); break; case GAME_EVENT_GUN_FIRE: { const GunDescription *g = StrGunDescription(e.u.GunFire.Gun); const Vec2i fullPos = Net2Vec2i(e.u.GunFire.MuzzleFullPos); // Add bullets if (g->Bullet && !gCampaign.IsClient) { // Find the starting angle of the spread (clockwise) // Keep in mind the fencepost problem, i.e. spread of 3 means a // total spread angle of 2x width const double spreadStartAngle = g->AngleOffset - (g->Spread.Count - 1) * g->Spread.Width / 2; for (int i = 0; i < g->Spread.Count; i++) { const double recoil = ((double)rand() / RAND_MAX * g->Recoil) - g->Recoil / 2; const double finalAngle = e.u.GunFire.Angle + spreadStartAngle + i * g->Spread.Width + recoil; GameEvent ab = GameEventNew(GAME_EVENT_ADD_BULLET); ab.u.AddBullet.UID = MobObjsObjsGetNextUID(); strcpy(ab.u.AddBullet.BulletClass, g->Bullet->Name); ab.u.AddBullet.MuzzlePos = Vec2i2Net(fullPos); ab.u.AddBullet.MuzzleHeight = e.u.GunFire.Z; ab.u.AddBullet.Angle = (float)finalAngle; ab.u.AddBullet.Elevation = RAND_INT(g->ElevationLow, g->ElevationHigh); ab.u.AddBullet.Flags = e.u.GunFire.Flags; ab.u.AddBullet.PlayerUID = e.u.GunFire.PlayerUID; ab.u.AddBullet.ActorUID = e.u.GunFire.UID; GameEventsEnqueue(&gGameEvents, ab); } } // Add muzzle flash if (GunHasMuzzle(g)) { GameEvent ap = GameEventNew(GAME_EVENT_ADD_PARTICLE); ap.u.AddParticle.Class = g->MuzzleFlash; ap.u.AddParticle.FullPos = fullPos; ap.u.AddParticle.Z = e.u.GunFire.Z; ap.u.AddParticle.Angle = e.u.GunFire.Angle; GameEventsEnqueue(&gGameEvents, ap); } // Sound if (e.u.GunFire.Sound && g->Sound) { SoundPlayAt(&gSoundDevice, g->Sound, Vec2iFull2Real(fullPos)); } // Screen shake if (g->ShakeAmount > 0) { GameEvent s = GameEventNew(GAME_EVENT_SCREEN_SHAKE); s.u.ShakeAmount = g->ShakeAmount; GameEventsEnqueue(&gGameEvents, s); } // Brass shells // If we have a reload lead, defer the creation of shells until then if (g->Brass && g->ReloadLead == 0) { const direction_e d = RadiansToDirection(e.u.GunFire.Angle); const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d); GunAddBrass(g, d, Vec2iMinus(fullPos, muzzleOffset)); } } break; case GAME_EVENT_GUN_RELOAD: { const GunDescription *g = StrGunDescription(e.u.GunReload.Gun); const Vec2i fullPos = Net2Vec2i(e.u.GunReload.FullPos); SoundPlayAtPlusDistance( &gSoundDevice, g->ReloadSound, Vec2iFull2Real(fullPos), RELOAD_DISTANCE_PLUS); // Brass shells if (g->Brass) { GunAddBrass(g, (direction_e)e.u.GunReload.Direction, fullPos); } } break; case GAME_EVENT_GUN_STATE: { const TActor *a = ActorGetByUID(e.u.GunState.ActorUID); if (!a->isInUse) break; WeaponSetState(ActorGetGun(a), (gunstate_e)e.u.GunState.State); } break; case GAME_EVENT_ADD_BULLET: BulletAdd(e.u.AddBullet); break; case GAME_EVENT_ADD_PARTICLE: ParticleAdd(&gParticles, e.u.AddParticle); break; case GAME_EVENT_ACTOR_HIT: { TActor *a = ActorGetByUID(e.u.ActorHit.UID); if (!a->isInUse) break; ActorTakeHit(a, e.u.ActorHit.Special); if (e.u.ActorHit.Power > 0) { DamageActor( a, e.u.ActorHit.Power, e.u.ActorHit.HitterPlayerUID); if (e.u.ActorHit.PlayerUID >= 0) { HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_HEALTH, e.u.ActorHit.PlayerUID, -e.u.ActorHit.Power); } AddBloodSplatter( a->Pos, e.u.ActorHit.Power, Net2Vec2i(e.u.ActorHit.Vel)); } } break; case GAME_EVENT_TRIGGER: { const Tile *t = MapGetTile(&gMap, Net2Vec2i(e.u.TriggerEvent.Tile)); CA_FOREACH(Trigger *, tp, t->triggers) if ((*tp)->id == (int)e.u.TriggerEvent.ID) { TriggerActivate(*tp, &gMap.triggers); break; } CA_FOREACH_END() } break; case GAME_EVENT_EXPLORE_TILES: // Process runs of explored tiles for (int i = 0; i < (int)e.u.ExploreTiles.Runs_count; i++) { Vec2i tile = Net2Vec2i(e.u.ExploreTiles.Runs[i].Tile); for (int j = 0; j < e.u.ExploreTiles.Runs[i].Run; j++) { MapMarkAsVisited(&gMap, tile); tile.x++; if (tile.x == gMap.Size.x) { tile.x = 0; tile.y++; } } } break; case GAME_EVENT_RESCUE_CHARACTER: { TActor *a = ActorGetByUID(e.u.Rescue.UID); if (!a->isInUse) break; a->flags &= ~FLAGS_PRISONER; SoundPlayAt( &gSoundDevice, StrSound("rescue"), Vec2iFull2Real(a->Pos)); } break; case GAME_EVENT_OBJECTIVE_UPDATE: { ObjectiveDef *o = CArrayGet( &gMission.Objectives, e.u.ObjectiveUpdate.ObjectiveId); o->done += e.u.ObjectiveUpdate.Count; // Display a text update effect for the objective HUDAddUpdate( &camera->HUD, NUMBER_UPDATE_OBJECTIVE, e.u.ObjectiveUpdate.ObjectiveId, e.u.ObjectiveUpdate.Count); MissionSetMessageIfComplete(&gMission); } break; case GAME_EVENT_ADD_KEYS: gMission.KeyFlags |= e.u.AddKeys.KeyFlags; SoundPlayAt( &gSoundDevice, gSoundDevice.keySound, Net2Vec2i(e.u.AddKeys.Pos)); // Clear cache since we may now have new paths PathCacheClear(&gPathCache); break; case GAME_EVENT_MISSION_COMPLETE: if (e.u.MissionComplete.ShowMsg) { HUDDisplayMessage(&camera->HUD, "Mission complete", -1); } camera->HUD.showExit = true; MapShowExitArea(&gMap); break; case GAME_EVENT_MISSION_INCOMPLETE: gMission.state = MISSION_STATE_PLAY; break; case GAME_EVENT_MISSION_PICKUP: gMission.state = MISSION_STATE_PICKUP; gMission.pickupTime = gMission.time; SoundPlay(&gSoundDevice, StrSound("whistle")); break; case GAME_EVENT_MISSION_END: gMission.isDone = true; break; default: assert(0 && "unknown game event"); break; } }