void DrawHead( const Character *c, const direction_e dir, const Vec2i pos) { const Pic *head = GetHeadPic(c->Class, dir, GUNSTATE_READY); const Vec2i drawPos = Vec2iMinus(pos, Vec2iNew( head->size.x / 2, head->size.y / 2)); BlitCharMultichannel(&gGraphicsDevice, head, drawPos, &c->Colors); }
static void DrawBody(GraphicsDevice *g, const ActorPics *pics, const Vec2i pos) { const Pic *body = pics->Body; const Vec2i drawPos = Vec2iMinus(pos, Vec2iNew( body->size.x / 2, body->size.y / 2 + FOOT_OFFSET)); const color_t mask = pics->Mask != NULL ? *pics->Mask : colorWhite; BlitMasked(g, pics->Body, drawPos, mask, true); }
bool UITryGetObject(UIObject *o, Vec2i pos, UIObject **out) { if (!o->IsVisible) { return false; } bool isHighlighted = UIObjectIsHighlighted(o); if (o->Type == UITYPE_TAB) { // only recurse to the chosen child if (o->Children.size > 0) { UIObject **objp = CArrayGet(&o->Children, o->u.Tab.Index); if ((!((*objp)->Flags & UI_ENABLED_WHEN_PARENT_HIGHLIGHTED_ONLY) || isHighlighted) && (*objp)->IsVisible && UITryGetObject(*objp, Vec2iMinus(pos, o->Pos), out)) { return true; } } } else { size_t i; UIObject **objs = o->Children.data; for (i = 0; i < o->Children.size; i++, objs++) { if ((!((*objs)->Flags & UI_ENABLED_WHEN_PARENT_HIGHLIGHTED_ONLY) || isHighlighted) && (*objs)->IsVisible && UITryGetObject(*objs, Vec2iMinus(pos, o->Pos), out)) { return true; } } } if (IsInside(pos, o->Pos, o->Size) && o->Type != UITYPE_CONTEXT_MENU) { *out = o; return true; } return false; }
static void DrawPickupSpawner( UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { const IndexedEditorBrush *data = vData; const MapObject *mo = data->u.MapObject; DisplayMapItem( Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)), mo); const Pic *pic = mo->u.PickupClass->Pic; pos = Vec2iMinus(pos, Vec2iScaleDiv(pic->size, 2)); Blit(g, pic, Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2))); }
static Vec2i GetActorDrawOffset( const Pic *pic, const BodyPart part, const CharSprites *cs, const ActorAnimation anim, const int frame, const direction_e d) { Vec2i offset = Vec2iScaleDiv(pic->size, -2); offset = Vec2iMinus(offset, CharSpritesGetOffset( cs->Offsets.Frame[part], anim == ACTORANIMATION_WALKING ? "run" : "idle", frame)); offset = Vec2iAdd(offset, cs->Offsets.Dir[part][d]); return offset; }
void DrawKey(UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { EditorBrushAndCampaign *data = vData; if (data->Brush.ItemIndex == -1) { // No key; don't draw return; } const Pic *pic = KeyPickupClass(gMission.keyStyle, data->Brush.ItemIndex)->Pic; pos = Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)); pos = Vec2iMinus(pos, Vec2iScaleDiv(pic->size, 2)); Blit(g, pic, pos); }
static void DrawThing(DrawBuffer *b, const TTileItem *t, const Vec2i offset) { const Vec2i picPos = Vec2iNew( t->x - b->xTop + offset.x, t->y - b->yTop + offset.y); if (!Vec2iIsZero(t->ShadowSize)) { DrawShadow(&gGraphicsDevice, picPos, t->ShadowSize); } if (t->CPicFunc) { CPicDrawContext c = t->CPicFunc(t->id); CPicDraw(b->g, &t->CPic, picPos, &c); } else if (t->getPicFunc) { Vec2i picOffset; const Pic *pic = t->getPicFunc(t->id, &picOffset); Blit(&gGraphicsDevice, pic, Vec2iAdd(picPos, picOffset)); } else if (t->kind == KIND_CHARACTER) { TActor *a = CArrayGet(&gActors, t->id); ActorPics pics = GetCharacterPicsFromActor(a); DrawActorPics(&pics, picPos); // Draw weapon indicators DrawLaserSight(&pics, a, picPos); } else { (*(t->drawFunc))(picPos, &t->drawData); } #ifdef DEBUG_DRAW_HITBOXES const int pulsePeriod = ConfigGetInt(&gConfig, "Game.FPS"); int alphaUnscaled = (gMission.time % pulsePeriod) * 255 / (pulsePeriod / 2); if (alphaUnscaled > 255) { alphaUnscaled = 255 * 2 - alphaUnscaled; } color_t color = colorPurple; color.a = (Uint8)alphaUnscaled; DrawRectangle( &gGraphicsDevice, Vec2iMinus(picPos, Vec2iScaleDiv(t->size, 2)), t->size, color, DRAW_FLAG_LINE); #endif }
static Vec2i SeekTowards( const Vec2i pos, const Vec2i vel, const double speedMin, const Vec2i targetPos, const int seekFactor) { // Compensate for bullet's velocity const Vec2i targetVel = Vec2iMinus(Vec2iMinus(targetPos, pos), vel); // Don't seek if the coordinates are too big if (abs(targetVel.x) > 10000 || abs(targetVel.y) > 10000 || Vec2iIsZero(targetVel)) { return vel; } const double targetMag = sqrt( targetVel.x*targetVel.x + targetVel.y*targetVel.y); const double magnitude = MAX(speedMin, Vec2iEqual(vel, Vec2iZero()) ? speedMin : sqrt(vel.x*vel.x + vel.y*vel.y)); const double combinedX = vel.x / magnitude * seekFactor + targetVel.x / targetMag; const double combinedY = vel.y / magnitude * seekFactor + targetVel.y / targetMag; return Vec2iNew( (int)round(combinedX * magnitude / (seekFactor + 1)), (int)round(combinedY * magnitude / (seekFactor + 1))); }
// TODO: reimplement in camera Vec2i GetPlayerCenter( GraphicsDevice *device, const Camera *camera, const PlayerData *pData, const int playerIdx) { if (pData->ActorUID < 0) { // Player is dead return Vec2iZero(); } Vec2i center = Vec2iZero(); int w = device->cachedConfig.Res.x; int h = device->cachedConfig.Res.y; if (GetNumPlayers(PLAYER_ALIVE_OR_DYING, true, true) == 1 || GetNumPlayers(PLAYER_ALIVE_OR_DYING, false , true) == 1 || CameraIsSingleScreen()) { const Vec2i pCenter = camera->lastPosition; const Vec2i screenCenter = Vec2iNew(w / 2, device->cachedConfig.Res.y / 2); const TActor *actor = ActorGetByUID(pData->ActorUID); const Vec2i p = Vec2iNew(actor->tileItem.x, actor->tileItem.y); center = Vec2iAdd(Vec2iMinus(p, pCenter), screenCenter); } else { const int numLocalPlayers = GetNumPlayers(PLAYER_ANY, false, true); if (numLocalPlayers == 2) { center.x = playerIdx == 0 ? w / 4 : w * 3 / 4; center.y = h / 2; } else if (numLocalPlayers >= 3 && numLocalPlayers <= 4) { center.x = (playerIdx & 1) ? w * 3 / 4 : w / 4; center.y = (playerIdx >= 2) ? h * 3 / 4 : h / 4; } else { CASSERT(false, "invalid number of players"); } } return center; }
static void DrawObjectiveInfo(const Objective *o, const Vec2i pos) { const CharacterStore *store = &gCampaign.Setting.characters; switch (o->Type) { case OBJECTIVE_KILL: { const Character *cd = CArrayGet( &store->OtherChars, CharacterStoreGetSpecialId(store, 0)); DrawHead(cd, DIRECTION_DOWN, STATE_IDLE, pos); } break; case OBJECTIVE_RESCUE: { const Character *cd = CArrayGet( &store->OtherChars, CharacterStoreGetPrisonerId(store, 0)); DrawHead(cd, DIRECTION_DOWN, STATE_IDLE, pos); } break; case OBJECTIVE_COLLECT: { const Pic *p = o->u.Pickup->Pic; Blit(&gGraphicsDevice, p, Vec2iMinus(pos, Vec2iScaleDiv(p->size, 2))); } break; case OBJECTIVE_DESTROY: { Vec2i picOffset; const Pic *p = MapObjectGetPic(o->u.MapObject, &picOffset, false); Blit(&gGraphicsDevice, p, Vec2iAdd(pos, picOffset)); } break; case OBJECTIVE_INVESTIGATE: // Don't draw return; default: CASSERT(false, "Unknown objective type"); return; } }
static void DrawObjective( UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { UNUSED(g); EditorBrushAndCampaign *data = vData; Mission *m = CampaignGetCurrentMission(data->Campaign); const Objective *obj = CArrayGet(&m->Objectives, data->Brush.u.ItemIndex); CharacterStore *store = &data->Campaign->Setting.characters; pos = Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)); switch (obj->Type) { case OBJECTIVE_KILL: { Character *c = CArrayGet( &store->OtherChars, CharacterStoreGetSpecialId(store, data->Brush.Index2)); DrawCharacterSimple(c, pos, DIRECTION_DOWN, false, false); } break; case OBJECTIVE_RESCUE: { Character *c = CArrayGet( &store->OtherChars, CharacterStoreGetPrisonerId(store, data->Brush.Index2)); DrawCharacterSimple(c, pos, DIRECTION_DOWN, false, false); } break; case OBJECTIVE_COLLECT: { const Pic *p = obj->u.Pickup->Pic; pos = Vec2iMinus(pos, Vec2iScaleDiv(p->size, 2)); Blit(&gGraphicsDevice, p, pos); } break; case OBJECTIVE_DESTROY: DisplayMapItem(pos, obj->u.MapObject); break; default: assert(0 && "invalid objective type"); break; } }
static Vec2i GetActorDrawOffset( const Vec2i pos, const Pic *pic, const BodyPart part, const direction_e d) { Vec2i outPos = Vec2iMinus(pos, Vec2iScaleDiv(pic->size, 2)); switch (part) { case BODY_PART_HEAD: outPos.y -= NECK_OFFSET; break; case BODY_PART_BODY: outPos.y -= FOOT_OFFSET; break; case BODY_PART_GUN: outPos = Vec2iAdd(outPos, cGunHandOffset[d]); outPos.y -= WRIST_OFFSET; break; default: CASSERT(false, "unknown body part"); break; } return outPos; }
static void AddBrass( const GunDescription *g, const direction_e d, const Vec2i pos) { CASSERT(g->Brass, "Cannot create brass for no-brass weapon"); GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE); e.u.AddParticle.Class = g->Brass; double x, y; const double radians = dir2radians[d]; GetVectorsForRadians(radians, &x, &y); const Vec2i ejectionPortOffset = Vec2iReal2Full(Vec2iScale(Vec2iNew( (int)round(x), (int)round(y)), 7)); const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d); const Vec2i muzzlePosition = Vec2iAdd(pos, muzzleOffset); e.u.AddParticle.FullPos = Vec2iMinus(muzzlePosition, ejectionPortOffset); e.u.AddParticle.Z = g->MuzzleHeight; e.u.AddParticle.Vel = Vec2iScaleDiv( GetFullVectorsForRadians(radians + PI / 2), 3); e.u.AddParticle.Vel.x += (rand() % 128) - 64; e.u.AddParticle.Vel.y += (rand() % 128) - 64; e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); e.u.AddParticle.DZ = (rand() % 6) + 6; e.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, e); }
static HandleInputResult HandleInput( int c, int m, int *xc, int *yc, int *xcOld, int *ycOld, Mission *scrap) { HandleInputResult result = { false, false, false, false }; Mission *mission = CampaignGetCurrentMission(&gCampaign); UIObject *o = NULL; brush.Pos = GetMouseTile(&gEventHandlers); // Find whether the mouse has hovered over a tooltip bool hadTooltip = sTooltipObj != NULL; if (!UITryGetObject(sObjs, gEventHandlers.mouse.currentPos, &sTooltipObj) || !sTooltipObj->Tooltip) { sTooltipObj = NULL; } // Need to redraw if we either had a tooltip (draw to remove) or there's a // tooltip to draw if (hadTooltip || sTooltipObj) { result.Redraw = true; } // Make sure a redraw is done immediately if the resolution changes // Otherwise the resolution change is ignored and we try to redraw // later, when the draw buffer has not yet been recreated if (gEventHandlers.HasResolutionChanged) { result.Redraw = true; } // Also need to redraw if the brush is active to update the highlight if (brush.IsActive) { result.Redraw = true; } if (m && (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_RIGHT || m == SDL_BUTTON_WHEELUP || m == SDL_BUTTON_WHEELDOWN)) { result.Redraw = true; if (UITryGetObject(sObjs, gEventHandlers.mouse.currentPos, &o)) { if (!o->DoNotHighlight) { if (sLastHighlightedObj) { UIObjectUnhighlight(sLastHighlightedObj); } sLastHighlightedObj = o; UIObjectHighlight(o); } CArrayTerminate(&sDrawObjs); *xcOld = *xc; *ycOld = *yc; // Only change selection on left/right click if (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_RIGHT) { if (!(o->Flags & UI_LEAVE_YC)) { *yc = o->Id; AdjustYC(yc); } if (!(o->Flags & UI_LEAVE_XC)) { *xc = o->Id2; AdjustXC(*yc, xc); } } if (!(o->Flags & UI_SELECT_ONLY) && (!(o->Flags & UI_SELECT_ONLY_FIRST) || (*xc == *xcOld && *yc == *ycOld))) { if (m == SDL_BUTTON_LEFT || m == SDL_BUTTON_WHEELUP) { c = SDLK_PAGEUP; } else if (m == SDL_BUTTON_RIGHT || m == SDL_BUTTON_WHEELDOWN) { c = SDLK_PAGEDOWN; } } } else { if (!(brush.IsActive && mission)) { UIObjectUnhighlight(sObjs); CArrayTerminate(&sDrawObjs); sLastHighlightedObj = NULL; } } } if (!o && (MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_LEFT) || MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_RIGHT))) { result.Redraw = true; if (brush.IsActive && mission->Type == MAPTYPE_STATIC) { // Draw a tile if (IsBrushPosValid(brush.Pos, mission)) { int isMain = MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_LEFT); EditorResult r = EditorBrushStartPainting(&brush, mission, isMain); if (r == EDITOR_RESULT_CHANGED || r == EDITOR_RESULT_CHANGED_AND_RELOAD) { fileChanged = 1; Autosave(); result.RemakeBg = true; sHasUnbakedChanges = true; } if (r == EDITOR_RESULT_CHANGED_AND_RELOAD) { Setup(0); } } } } else { if (mission) { // Clamp brush position brush.Pos = Vec2iClamp( brush.Pos, Vec2iZero(), Vec2iMinus(mission->Size, Vec2iUnit())); EditorResult r = EditorBrushStopPainting(&brush, mission); if (r == EDITOR_RESULT_CHANGED || r == EDITOR_RESULT_CHANGED_AND_RELOAD) { fileChanged = 1; Autosave(); result.Redraw = true; result.RemakeBg = true; sHasUnbakedChanges = true; } if (r == EDITOR_RESULT_CHANGED_AND_RELOAD) { Setup(0); } } } // Pan the camera based on keyboard cursor keys if (mission) { if (KeyIsDown(&gEventHandlers.keyboard, SDLK_LEFT)) { camera.x -= CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } else if (KeyIsDown(&gEventHandlers.keyboard, SDLK_RIGHT)) { camera.x += CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } if (KeyIsDown(&gEventHandlers.keyboard, SDLK_UP)) { camera.y -= CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } else if (KeyIsDown(&gEventHandlers.keyboard, SDLK_DOWN)) { camera.y += CAMERA_PAN_SPEED; result.Redraw = result.RemakeBg = true; } // Also pan the camera based on middle mouse drag if (MouseIsDown(&gEventHandlers.mouse, SDL_BUTTON_MIDDLE)) { camera = Vec2iAdd(camera, Vec2iMinus( gEventHandlers.mouse.previousPos, gEventHandlers.mouse.currentPos)); result.Redraw = result.RemakeBg = true; } camera.x = CLAMP(camera.x, 0, Vec2iCenterOfTile(mission->Size).x); camera.y = CLAMP(camera.y, 0, Vec2iCenterOfTile(mission->Size).y); } bool hasQuit = false; if (gEventHandlers.keyboard.modState & (KMOD_ALT | KMOD_CTRL)) { result.Redraw = true; switch (c) { case 'z': // Undo // Do this by swapping the current mission with the last mission // This requires a bit of copy-acrobatics; because missions // are saved in Setup(), but by this stage the mission has already // changed, _two_ mission caches are used, copied in sequence. // That is, if the current mission is at state B, the first cache // is still at state B (copied after the mission has changed // already), and the second cache is at state A. // If we were to perform an undo and still maintain functionality, // we need to copy such that the states change from B,B,A to // A,A,B. // However! The above is true only if we have "baked" changes // The editor has been optimised to perform some changes // without reloading map files; that is, the files are actually // in states C,B,A. // In this case, another set of "acrobatics" is required if (sHasUnbakedChanges) { MissionCopy(&lastMission, mission); // B,A,Z -> B,A,B } else { MissionCopy(mission, &lastMission); // B,B,A -> A,B,A MissionCopy(&lastMission, ¤tMission); // A,B,A -> A,B,B } fileChanged = 1; Setup(0); // A,B,B -> A,A,B break; case 'x': MissionTerminate(scrap); MissionCopy(scrap, mission); Delete(*xc, *yc); break; case 'c': MissionTerminate(scrap); MissionCopy(scrap, mission); break; case 'v': // Use map size as a proxy to whether there's a valid scrap mission if (!Vec2iEqual(scrap->Size, Vec2iZero())) { InsertMission(&gCampaign, scrap, gCampaign.MissionIndex); fileChanged = 1; Setup(0); } break; case 'q': hasQuit = true; break; case 'n': InsertMission(&gCampaign, NULL, gCampaign.Setting.Missions.size); gCampaign.MissionIndex = gCampaign.Setting.Missions.size - 1; fileChanged = 1; Setup(0); break; case 'o': if (!fileChanged || ConfirmScreen( "File has been modified, but not saved", "Open anyway? (Y/N)")) { Open(); } break; case 's': Save(); break; case 'm': result.WillDisplayAutomap = true; break; case 'e': EditCharacters(&gCampaign.Setting); Setup(0); UIObjectUnhighlight(sObjs); CArrayTerminate(&sDrawObjs); break; } } else { if (c != 0) { result.Redraw = true; } switch (c) { case SDLK_F1: HelpScreen(); break; case SDLK_HOME: if (gCampaign.MissionIndex > 0) { gCampaign.MissionIndex--; } Setup(0); break; case SDLK_END: if (gCampaign.MissionIndex < (int)gCampaign.Setting.Missions.size) { gCampaign.MissionIndex++; } Setup(0); break; case SDLK_INSERT: switch (*yc) { case YC_CHARACTERS: if (gCampaign.Setting.characters.OtherChars.size > 0) { int ch = 0; CArrayPushBack(&mission->Enemies, &ch); CharacterStoreAddBaddie(&gCampaign.Setting.characters, ch); *xc = mission->Enemies.size - 1; } break; case YC_SPECIALS: if (gCampaign.Setting.characters.OtherChars.size > 0) { int ch = 0; CArrayPushBack(&mission->SpecialChars, &ch); CharacterStoreAddSpecial(&gCampaign.Setting.characters, ch); *xc = mission->SpecialChars.size - 1; } break; case YC_ITEMS: { int item = 0; CArrayPushBack(&mission->Items, &item); CArrayPushBack(&mission->ItemDensities, &item); *xc = mission->Items.size - 1; } break; default: if (*yc >= YC_OBJECTIVES) { AddObjective(mission); } else { InsertMission(&gCampaign, NULL, gCampaign.MissionIndex); } break; } fileChanged = 1; Setup(0); break; case SDLK_DELETE: Delete(*xc, *yc); break; case SDLK_PAGEUP: if (Change(o, *yc, 1)) { fileChanged = 1; } Setup(0); break; case SDLK_PAGEDOWN: if (Change(o, *yc, -1)) { fileChanged = 1; } Setup(0); break; case SDLK_ESCAPE: hasQuit = true; break; case SDLK_BACKSPACE: fileChanged |= UIObjectDelChar(sObjs); break; default: c = KeyGetTyped(&gEventHandlers.keyboard); if (c) { fileChanged |= UIObjectAddChar(sObjs, (char)c); } break; } } if (gEventHandlers.HasQuit) { hasQuit = true; } if (hasQuit && (!fileChanged || ConfirmScreen( "File has been modified, but not saved", "Quit anyway? (Y/N)"))) { result.Done = true; } return result; }
// Perform LOS by casting rays from the centre to the edges, terminating // whenever an obstruction or out-of-range is reached. void DrawBufferLOS(DrawBuffer *buffer, Vec2i center) { int sightRange = gConfig.Game.SightRange; // Note: can be zero LOSData data; data.b = buffer; data.center.x = center.x / TILE_WIDTH - buffer->xStart; data.center.y = center.y / TILE_HEIGHT - buffer->yStart; data.sightRange2 = sightRange * sightRange; // First mark center tile and all adjacent tiles as visible // +-+-+-+ // |V|V|V| // +-+-+-+ // |V|C|V| // +-+-+-+ // |V|V|V| (C=center, V=visible) // +-+-+-+ Vec2i end; for (end.x = data.center.x - 1; end.x < data.center.x + 2; end.x++) { for (end.y = data.center.y - 1; end.y < data.center.y + 2; end.y++) { Tile *tile = GetTile(buffer, end); if (tile) { tile->flags |= MAPTILE_IS_VISIBLE; } } } // Work out the perimeter of the LOS casts Vec2i origin = Vec2iZero(); if (sightRange > 0) { // Limit the perimeter to the sight range origin.x = MAX(origin.x, data.center.x - sightRange); origin.y = MAX(origin.y, data.center.y - sightRange); } Vec2i perimSize = Vec2iScale(Vec2iMinus(data.center, origin), 2); // Start from the top-left cell, and proceed clockwise around end = origin; HasClearLineData lineData; lineData.IsBlocked = IsNextTileBlockedAndSetVisibility; lineData.data = &data; // Top edge for (; end.x < origin.x + perimSize.x; end.x++) { HasClearLineXiaolinWu(data.center, end, &lineData); } // right edge for (; end.y < origin.y + perimSize.y; end.y++) { HasClearLineXiaolinWu(data.center, end, &lineData); } // bottom edge for (; end.x > origin.x; end.x--) { HasClearLineXiaolinWu(data.center, end, &lineData); } // left edge for (; end.y > origin.y; end.y--) { HasClearLineXiaolinWu(data.center, end, &lineData); } // Second pass: make any non-visible obstructions that are adjacent to // visible non-obstructions visible too // This is to ensure runs of walls stay visible for (end.y = origin.y; end.y < origin.y + perimSize.y; end.y++) { for (end.x = origin.x; end.x < origin.x + perimSize.x; end.x++) { Tile *tile = GetTile(buffer, end); if (!tile || !(tile->flags & MAPTILE_NO_SEE)) { continue; } // Check sight range if (data.sightRange2 > 0 && DistanceSquared(data.center, end) >= data.sightRange2) { continue; } SetObstructionVisible(buffer, end, tile); } } }
EditorResult EditorBrushStopPainting(EditorBrush *b, Mission *m) { EditorResult result = EDITOR_RESULT_NONE; if (b->IsPainting) { switch (b->Type) { case BRUSHTYPE_LINE: EditorBrushPaintLine(b, m); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_BOX: EditorBrushPaintBox(b, m, b->PaintType, MAP_UNSET); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_BOX_FILLED: EditorBrushPaintBox(b, m, b->PaintType, b->PaintType); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_ROOM: EditorBrushPaintBox(b, m, MAP_WALL, MAP_ROOM); result = EDITOR_RESULT_CHANGED; break; case BRUSHTYPE_ROOM_PAINTER: // Reload map to update tiles result = EDITOR_RESULT_RELOAD; break; case BRUSHTYPE_SELECT: if (b->IsMoving) { // Move the tiles from the source to the target // Need to copy all the tiles to a temp buffer first in case // we are moving to an overlapped position CArray movedTiles; Vec2i v; int i; int delta; CArrayInit(&movedTiles, sizeof(unsigned short)); // Copy tiles to temp from selection, setting them to MAP_FLOOR // in the process for (v.y = 0; v.y < b->SelectionSize.y; v.y++) { for (v.x = 0; v.x < b->SelectionSize.x; v.x++) { Vec2i vOffset = Vec2iAdd(v, b->SelectionStart); int idx = vOffset.y * m->Size.x + vOffset.x; unsigned short *tile = CArrayGet( &m->u.Static.Tiles, idx); CArrayPushBack(&movedTiles, tile); *tile = MAP_FLOOR; } } // Move the selection to the new position b->SelectionStart.x += b->Pos.x - b->DragPos.x; b->SelectionStart.y += b->Pos.y - b->DragPos.y; // Copy tiles to the new area, for parts of the new area that // are valid i = 0; for (v.y = 0; v.y < b->SelectionSize.y; v.y++) { for (v.x = 0; v.x < b->SelectionSize.x; v.x++) { Vec2i vOffset = Vec2iAdd(v, b->SelectionStart); if (vOffset.x >= 0 && vOffset.x < m->Size.x && vOffset.y >= 0 && vOffset.y < m->Size.y) { int idx = vOffset.y * m->Size.x + vOffset.x; unsigned short *tileFrom = CArrayGet(&movedTiles, i); unsigned short *tileTo = CArrayGet( &m->u.Static.Tiles, idx); *tileTo = *tileFrom; result = EDITOR_RESULT_CHANGED_AND_RELOAD; } i++; } } // Update the selection to fit within map boundaries delta = -b->SelectionStart.x; if (delta > 0) { b->SelectionStart.x += delta; b->SelectionSize.x -= delta; } delta = -b->SelectionStart.y; if (delta > 0) { b->SelectionStart.y += delta; b->SelectionSize.y -= delta; } delta = b->SelectionStart.x + b->SelectionSize.x - m->Size.x; if (delta > 0) { b->SelectionSize.x -= delta; } delta = b->SelectionStart.y + b->SelectionSize.y - m->Size.y; if (delta > 0) { b->SelectionSize.y -= delta; } // Check if the selection is still valid; if not, invalidate it if (b->SelectionSize.x < 0 || b->SelectionSize.y < 0) { b->SelectionSize = Vec2iZero(); } b->IsMoving = 0; } else { // Record the selection size b->SelectionStart = Vec2iMin(b->LastPos, b->Pos); b->SelectionSize.x = abs(b->LastPos.x - b->Pos.x) + 1; b->SelectionSize.y = abs(b->LastPos.y - b->Pos.y) + 1; // Disallow 1x1 selection sizes if (b->SelectionSize.x <= 1 && b->SelectionSize.y <= 1) { b->SelectionSize = Vec2iZero(); } } break; case BRUSHTYPE_SET_EXIT: { Vec2i exitStart = Vec2iMin(b->LastPos, b->Pos); Vec2i exitEnd = Vec2iMax(b->LastPos, b->Pos); // Clamp within map boundaries exitStart = Vec2iClamp( exitStart, Vec2iZero(), Vec2iMinus(m->Size, Vec2iUnit())); exitEnd = Vec2iClamp( exitEnd, Vec2iZero(), Vec2iMinus(m->Size, Vec2iUnit())); // Check that size is big enough Vec2i size = Vec2iAdd(Vec2iMinus(exitEnd, exitStart), Vec2iUnit()); if (size.x >= 3 && size.y >= 3) { // Check that exit area has changed if (!Vec2iEqual(exitStart, m->u.Static.Exit.Start) || !Vec2iEqual(exitEnd, m->u.Static.Exit.End)) { m->u.Static.Exit.Start = exitStart; m->u.Static.Exit.End = exitEnd; result = EDITOR_RESULT_CHANGED_AND_RELOAD; } } } break; default: // do nothing break; } } b->IsPainting = 0; CArrayClear(&b->HighlightedTiles); return result; }
static void DrawCompassArrow( GraphicsDevice *g, Rect2i r, Vec2i pos, Vec2i playerPos, color_t mask, const char *label) { Vec2i compassV = Vec2iMinus(pos, playerPos); // Don't draw if objective is on screen if (abs(pos.x - playerPos.x) < r.Size.x / 2 && abs(pos.y - playerPos.y) < r.Size.y / 2) { return; } Vec2i textPos = Vec2iZero(); // Find which edge of screen is the best bool hasDrawn = false; if (compassV.x != 0) { double sx = r.Size.x / 2.0 / compassV.x; int yInt = (int)floor(fabs(sx) * compassV.y + 0.5); if (yInt >= -r.Size.y / 2 && yInt <= r.Size.y / 2) { // Intercepts either left or right side hasDrawn = true; if (compassV.x > 0) { // right edge textPos = Vec2iNew( r.Pos.x + r.Size.x, r.Pos.y + r.Size.y / 2 + yInt); const Pic *p = PicManagerGetPic(&gPicManager, "arrow_right"); Vec2i drawPos = Vec2iNew( textPos.x - p->size.x, textPos.y - p->size.y / 2); BlitMasked(g, p, drawPos, mask, true); } else if (compassV.x < 0) { // left edge textPos = Vec2iNew(r.Pos.x, r.Pos.y + r.Size.y / 2 + yInt); const Pic *p = PicManagerGetPic(&gPicManager, "arrow_left"); Vec2i drawPos = Vec2iNew(textPos.x, textPos.y - p->size.y / 2); BlitMasked(g, p, drawPos, mask, true); } } } if (!hasDrawn && compassV.y != 0) { double sy = r.Size.y / 2.0 / compassV.y; int xInt = (int)floor(fabs(sy) * compassV.x + 0.5); if (xInt >= -r.Size.x / 2 && xInt <= r.Size.x / 2) { // Intercepts either top or bottom side if (compassV.y > 0) { // bottom edge textPos = Vec2iNew( r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y + r.Size.y); const Pic *p = PicManagerGetPic(&gPicManager, "arrow_down"); Vec2i drawPos = Vec2iNew( textPos.x - p->size.x / 2, textPos.y - p->size.y); BlitMasked(g, p, drawPos, mask, true); } else if (compassV.y < 0) { // top edge textPos = Vec2iNew(r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y); const Pic *p = PicManagerGetPic(&gPicManager, "arrow_up"); Vec2i drawPos = Vec2iNew(textPos.x - p->size.x / 2, textPos.y); BlitMasked(g, p, drawPos, mask, true); } } } if (label && strlen(label) > 0) { Vec2i textSize = FontStrSize(label); // Center the text around the target position textPos.x -= textSize.x / 2; textPos.y -= textSize.y / 2; // Make sure the text is inside the screen int padding = 8; textPos.x = MAX(textPos.x, r.Pos.x + padding); textPos.x = MIN(textPos.x, r.Pos.x + r.Size.x - textSize.x - padding); textPos.y = MAX(textPos.y, r.Pos.y + padding); textPos.y = MIN(textPos.y, r.Pos.y + r.Size.y - textSize.y - padding); FontStrMask(label, textPos, mask); } }
void HUDDraw(HUD *hud, const input_device_e pausingDevice) { char s[50]; int flags = 0; const int numPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false); const int numLocalPlayers = GetNumPlayers(PLAYER_ANY, false, true); const int numLocalPlayersAlive = GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, true); Rect2i r; r.Size = Vec2iNew( hud->device->cachedConfig.Res.x, hud->device->cachedConfig.Res.y); if (numLocalPlayersAlive <= 1) { flags = 0; } else if ( ConfigGetEnum(&gConfig, "Interface.Splitscreen") == SPLITSCREEN_NEVER) { flags |= HUDFLAGS_SHARE_SCREEN; } else if (numLocalPlayers == 2) { r.Size.x /= 2; flags |= HUDFLAGS_HALF_SCREEN; } else if (numLocalPlayers == 3 || numLocalPlayers == 4) { r.Size.x /= 2; r.Size.y /= 2; flags |= HUDFLAGS_QUARTER_SCREEN; } else { assert(0 && "not implemented"); } int idx = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!p->IsLocal) { idx--; continue; } int drawFlags = flags; r.Pos = Vec2iZero(); if (idx & 1) { r.Pos.x = r.Size.x; drawFlags |= HUDFLAGS_PLACE_RIGHT; } if (idx >= 2) { r.Pos.y = r.Size.y; drawFlags |= HUDFLAGS_PLACE_BOTTOM; } TActor *player = NULL; if (IsPlayerAlive(p)) { player = ActorGetByUID(p->ActorUID); } DrawPlayerStatus(hud, p, player, drawFlags, r); DrawScoreUpdate(&hud->scoreUpdates[idx], drawFlags); DrawHealthUpdate(&hud->healthUpdates[idx], drawFlags); DrawAmmoUpdate(&hud->ammoUpdates[idx], drawFlags); } // Only draw radar once if shared if (ConfigGetBool(&gConfig, "Interface.ShowHUDMap") && (flags & HUDFLAGS_SHARE_SCREEN) && IsAutoMapEnabled(gCampaign.Entry.Mode)) { DrawSharedRadar(hud->device, RADAR_SCALE, hud->showExit); } if (numPlayersAlive == 0) { if (AreAllPlayersDeadAndNoLives()) { if (!IsPVP(gCampaign.Entry.Mode)) { FontStrCenter("Game Over!"); } else { FontStrCenter("All Kill!"); } } } else if (hud->mission->state == MISSION_STATE_PICKUP) { int timeLeft = gMission.pickupTime + PICKUP_LIMIT - gMission.time; sprintf(s, "Pickup in %d seconds\n", (timeLeft + (FPS_FRAMELIMIT - 1)) / FPS_FRAMELIMIT); FontStrCenter(s); } if (pausingDevice != INPUT_DEVICE_UNSET) { Vec2i pos = Vec2iScaleDiv(Vec2iMinus( gGraphicsDevice.cachedConfig.Res, FontStrSize("Foo\nPress foo or bar to unpause\nBaz")), 2); const int x = pos.x; FontStr("<Paused>", pos); pos.y += FontH(); pos = FontStr("Press ", pos); color_t c = colorWhite; const char *buttonName = InputGetButtonNameColor(pausingDevice, 0, CMD_ESC, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" again to quit", pos); pos.x = x; pos.y += FontH(); pos = FontStr("Press ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON1, &c); pos = FontStrMask(buttonName, pos, c); pos = FontStr(" or ", pos); buttonName = InputGetButtonNameColor( pausingDevice, 0, CMD_BUTTON2, &c); pos = FontStrMask(buttonName, pos, c); FontStr(" to unpause", pos); } if (hud->messageTicks > 0 || hud->messageTicks == -1) { // Draw the message centered, and just below the automap Vec2i pos = Vec2iNew( (hud->device->cachedConfig.Res.x - FontStrW(hud->message)) / 2, AUTOMAP_SIZE + AUTOMAP_PADDING + AUTOMAP_PADDING); FontStrMask(hud->message, pos, colorCyan); } if (ConfigGetBool(&gConfig, "Interface.ShowFPS")) { FPSCounterDraw(&hud->fpsCounter); } if (ConfigGetBool(&gConfig, "Interface.ShowTime")) { WallClockDraw(&hud->clock); } DrawKeycards(hud); // Draw elapsed mission time as MM:SS int missionTimeSeconds = gMission.time / FPS_FRAMELIMIT; sprintf(s, "%d:%02d", missionTimeSeconds / 60, missionTimeSeconds % 60); FontOpts opts = FontOptsNew(); opts.HAlign = ALIGN_CENTER; opts.Area = hud->device->cachedConfig.Res; opts.Pad.y = 5; FontStrOpt(s, Vec2iZero(), opts); if (HasObjectives(gCampaign.Entry.Mode)) { DrawObjectiveCounts(hud); } }
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; } }