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))); }
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); }
void GrafxMakeRandomBackground( GraphicsDevice *device, CampaignOptions *co, struct MissionOptions *mo, Map *map) { HSV tint; CampaignSettingInit(&co->Setting); ActorsInit(); ObjsInit(); MobObjsInit(); SetupQuickPlayCampaign(&co->Setting, &gConfig.QuickPlay); co->seed = rand(); tint.h = rand() * 360.0 / RAND_MAX; tint.s = rand() * 1.0 / RAND_MAX; tint.v = 0.5; DrawBuffer buffer; DrawBufferInit(&buffer, Vec2iNew(X_TILES, Y_TILES), device); co->MissionIndex = 0; GrafxMakeBackground( device, &buffer, co, mo, map, tint, 0, 1, Vec2iCenterOfTile(Vec2iScaleDiv(map->Size, 2)), NULL); DrawBufferTerminate(&buffer); ActorsTerminate(); ObjsTerminate(); MobObjsTerminate(); RemoveAllWatches(); MissionOptionsTerminate(mo); CampaignSettingTerminate(&co->Setting); co->seed = gConfig.Game.RandomSeed; }
const Pic *GetObjectPic(const int id, Vec2i *offset) { const TObject *obj = CArrayGet(&gObjs, id); Pic *pic = NULL; // Try to get new pic if available if (obj->picName && obj->picName[0] != '\0') { pic = PicManagerGetPic(&gPicManager, obj->picName); } // Use new pic offset if old one unavailable const TOffsetPic *ofpic = obj->pic; if (!ofpic) { // If new one also unavailable, bail if (pic == NULL) { return NULL; } *offset = Vec2iScaleDiv(pic->size, -2); } else if (pic == NULL) { // Default old pic pic = PicManagerGetFromOld(&gPicManager, ofpic->picIndex); *offset = pic->offset; } if (ofpic != NULL) { *offset = Vec2iNew(ofpic->dx, ofpic->dy); } return pic; }
bool AreasCollide( const Vec2i pos1, const Vec2i pos2, const Vec2i size1, const Vec2i size2) { const Vec2i d = Vec2iNew(abs(pos1.x - pos2.x), abs(pos1.y - pos2.y)); const Vec2i r = Vec2iScaleDiv(Vec2iAdd(size1, size2), 2); return d.x < r.x && d.y < r.y; }
static void MakeBackground(GraphicsDevice *g, int buildTables) { if (buildTables) { // Automatically pan camera to middle of screen Mission *m = gMission.missionData; Vec2i focusTile = Vec2iScaleDiv(m->Size, 2); // Better yet, if the map has a known start position, focus on that if (m->Type == MAPTYPE_STATIC && !Vec2iEqual(m->u.Static.Start, Vec2iZero())) { focusTile = m->u.Static.Start; } camera = Vec2iCenterOfTile(focusTile); } // Clear background first for (int i = 0; i < GraphicsGetScreenSize(&g->cachedConfig); i++) { g->buf[i] = PixelFromColor(g, colorBlack); } GrafxDrawExtra extra; extra.guideImage = brush.GuideImageSurface; extra.guideImageAlpha = brush.GuideImageAlpha; DrawBufferTerminate(&sDrawBuffer); DrawBufferInit(&sDrawBuffer, Vec2iNew(X_TILES, Y_TILES), &gGraphicsDevice); GrafxMakeBackground( g, &sDrawBuffer, &gCampaign, &gMission, &gMap, tintNone, 1, buildTables, camera, &extra); }
static void DoDamageCharacter( const Vec2i hitVector, const int power, const int flags, const int playerUID, const int uid, TActor *actor, const special_damage_e special) { // Create events: hit, damage, score CASSERT(actor->isInUse, "Cannot damage nonexistent player"); CASSERT(CanHitCharacter(flags, uid, actor), "damaging undamageable actor"); if (ConfigGetBool(&gConfig, "Game.ShotsPushback")) { GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE); ei.u.ActorImpulse.UID = actor->uid; ei.u.ActorImpulse.Vel = Vec2i2Net(Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR)); ei.u.ActorImpulse.Pos = Vec2i2Net(actor->Pos); GameEventsEnqueue(&gGameEvents, ei); } const bool canDamage = CanDamageCharacter(flags, playerUID, uid, actor, special); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_HIT); e.u.ActorHit.UID = actor->uid; e.u.ActorHit.PlayerUID = actor->PlayerUID; e.u.ActorHit.HitterPlayerUID = playerUID; e.u.ActorHit.Special = special; e.u.ActorHit.Power = canDamage ? power : 0; e.u.ActorHit.Vel = Vec2i2Net(hitVector); GameEventsEnqueue(&gGameEvents, e); if (canDamage) { // Don't score for friendly or player hits const bool isFriendly = (actor->flags & FLAGS_GOOD_GUY) || (!IsPVP(gCampaign.Entry.Mode) && actor->PlayerUID >= 0); if (playerUID >= 0 && power != 0 && !isFriendly) { // Calculate score based on // if they hit a penalty character e = GameEventNew(GAME_EVENT_SCORE); e.u.Score.PlayerUID = playerUID; if (actor->flags & FLAGS_PENALTY) { e.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e); } } }
// Check collision with a diamond shape // This means that the bounding box could be in collision, but the bounding // "radius" is not. The diamond is expressed with a single "radius" - that is, // the diamond is the same height and width. // This arrangement is used so that axis movement can slide off corners by // moving in a diagonal direction. // E.g. this is not a collision: // x // x x // x x // x x // x x // x x wwwww // x w // w // Where 'x' denotes the bounding diamond, and 'w' represents a wall corner. bool IsCollisionDiamond(const Map *map, const Vec2i pos, const Vec2i fullSize) { const Vec2i mapSize = Vec2iNew(map->Size.x * TILE_WIDTH, map->Size.y * TILE_HEIGHT); const Vec2i size = Vec2iScaleDiv(fullSize, 2); if (pos.x - size.x < 0 || pos.x + size.x >= mapSize.x || pos.y - size.y < 0 || pos.y + size.y >= mapSize.y) { return true; } // Only support wider-than-taller collision diamonds for now CASSERT(size.x >= size.y, "not implemented, taller than wider diamond"); const double gradient = (double)size.y / size.x; // Now we need to check in a diamond pattern that the boundary does not // collide // Top to right for (int i = 0; i < size.x; i++) { const int y = (int)Round((-size.x + i)* gradient); const Vec2i p = Vec2iAdd(pos, Vec2iNew(i, y)); if (HitWall(p.x, p.y)) { return true; } } // Right to bottom for (int i = 0; i < size.x; i++) { const int y = (int)Round(i * gradient); const Vec2i p = Vec2iAdd(pos, Vec2iNew(size.x - i, y)); if (HitWall(p.x, p.y)) { return true; } } // Bottom to left for (int i = 0; i < size.x; i++) { const int y = (int)Round((size.x - i) * gradient); const Vec2i p = Vec2iAdd(pos, Vec2iNew(-i, y)); if (HitWall(p.x, p.y)) { return true; } } // Left to top for (int i = 0; i < size.x; i++) { const int y = (int)Round(-i * gradient); const Vec2i p = Vec2iAdd(pos, Vec2iNew(-size.x + i, y)); if (HitWall(p.x, p.y)) { return true; } } return false; }
static void DrawMapItem( UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { UNUSED(g); const EditorBrush *brush = vData; DisplayMapItem( Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)), brush->u.MapObject); }
static void LoadMapObject(MapObject *m, json_t *node) { memset(m, 0, sizeof *m); m->Idx = -1; LoadInt(&m->Idx, node, "Index"); m->Name = GetString(node, "Name"); LoadPic(&m->Normal.Pic, node, "Pic", "OldPic"); LoadPic(&m->Wreck.Pic, node, "WreckPic", "OldWreckPic"); if (m->Normal.Pic) { // Default offset: centered X, align bottom of tile and sprite m->Normal.Offset = Vec2iNew( -m->Normal.Pic->size.x / 2, TILE_HEIGHT / 2 - m->Normal.Pic->size.y); LoadVec2i(&m->Normal.Offset, node, "Offset"); } if (m->Wreck.Pic) { m->Wreck.Offset = Vec2iScaleDiv(m->Wreck.Pic->size, -2); LoadVec2i(&m->Wreck.Offset, node, "WreckOffset"); } // Default tile size m->Size = Vec2iNew(TILE_WIDTH, TILE_HEIGHT); LoadVec2i(&m->Size, node, "Size"); LoadInt(&m->Health, node, "Health"); LoadBulletGuns(&m->DestroyGuns, node, "DestroyGuns"); json_t *flagsNode = json_find_first_label(node, "Flags"); if (flagsNode != NULL && flagsNode->child != NULL) { for (json_t *flagNode = flagsNode->child->child; flagNode; flagNode = flagNode->next) { m->Flags |= 1 << StrPlacementFlag(flagNode->text); } } // Special types JSON_UTILS_LOAD_ENUM(m->Type, node, "Type", StrMapObjectType); switch (m->Type) { case MAP_OBJECT_TYPE_NORMAL: // Do nothing break; case MAP_OBJECT_TYPE_PICKUP_SPAWNER: { char *tmp = GetString(node, "Pickup"); m->u.PickupClass = StrPickupClass(tmp); CFREE(tmp); } break; default: CASSERT(false, "unknown error"); break; } }
void DrawKey(UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { UNUSED(g); EditorBrushAndCampaign *data = vData; PicPaletted *keyPic = PicManagerGetOldPic( &gPicManager, cGeneralPics[gMission.keyPics[data->Brush.ItemIndex]].picIndex); pos = Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)); DrawTPic(pos.x, pos.y, keyPic); }
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 void DrawCharacter( UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { UNUSED(g); EditorBrushAndCampaign *data = vData; CharacterStore *store = &data->Campaign->Setting.characters; Character *c = CArrayGet(&store->OtherChars, data->Brush.u.ItemIndex); DrawCharacterSimple( c, Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)), DIRECTION_DOWN, false, false); }
static void DrawWreck( UIObject *o, GraphicsDevice *g, Vec2i pos, void *vData) { const IndexedEditorBrush *data = vData; const char **name = CArrayGet(&gMapObjects.Destructibles, data->u.ItemIndex); const MapObject *mo = StrMapObject(*name); pos = Vec2iAdd(Vec2iAdd(pos, o->Pos), Vec2iScaleDiv(o->Size, 2)); Vec2i offset; const Pic *pic = MapObjectGetPic(mo, &offset, true); Blit(g, pic, Vec2iAdd(pos, offset)); }
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 MousePrePoll(Mouse *mouse) { memset(mouse->pressedButtons, 0, sizeof mouse->pressedButtons); memcpy( mouse->previousButtons, mouse->currentButtons, sizeof mouse->previousButtons); mouse->previousPos = mouse->currentPos; SDL_GetMouseState(&mouse->currentPos.x, &mouse->currentPos.y); mouse->currentPos = Vec2iScaleDiv(mouse->currentPos, gConfig.Graphics.ScaleFactor); }
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 void PlayerSelectionDraw(void *data) { const PlayerSelectionData *pData = data; GraphicsBlitBkg(&gGraphicsDevice); const int w = gGraphicsDevice.cachedConfig.Res.x; const int h = gGraphicsDevice.cachedConfig.Res.y; int idx = 0; for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++) { const PlayerData *p = CArrayGet(&gPlayerDatas, i); if (!p->IsLocal) { idx--; continue; } if (p->inputDevice != INPUT_DEVICE_UNSET) { MenuDisplay(&pData->menus[idx].ms); } else { Vec2i center = Vec2iZero(); const char *prompt = "Press Fire to join..."; const Vec2i offset = Vec2iScaleDiv(FontStrSize(prompt), -2); switch (GetNumPlayers(false, false, true)) { case 1: // Center of screen center = Vec2iNew(w / 2, h / 2); break; case 2: // Side by side center = Vec2iNew(idx * w / 2 + w / 4, h / 2); break; case 3: case 4: // Four corners center = Vec2iNew( (idx & 1) * w / 2 + w / 4, (idx / 2) * h / 2 + h / 4); break; default: CASSERT(false, "not implemented"); break; } FontStr(prompt, Vec2iAdd(center, offset)); } } }
static bool ItemsCollide( const TTileItem *item1, const TTileItem *item2, const Vec2i pos) { int dx = abs(pos.x - item2->x); int dy = abs(pos.y - item2->y); const Vec2i r = Vec2iScaleDiv(Vec2iAdd(item1->size, item2->size), 2); if (dx < r.x && dy < r.y) { int odx = abs(item1->x - item2->x); int ody = abs(item1->y - item2->y); if (dx <= odx || dy <= ody) { return 1; } } return 0; }
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 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; }
bool IsCollisionWithWall(const Vec2i pos, const Vec2i fullSize) { Vec2i size = Vec2iScaleDiv(fullSize, 2); if (pos.x - size.x < 0 || pos.y - size.y < 0 || pos.x + size.x >= gMap.Size.x * TILE_WIDTH || pos.y + size.y >= gMap.Size.y * TILE_HEIGHT) { return true; } if (HitWall(pos.x - size.x, pos.y - size.y) || HitWall(pos.x - size.x, pos.y) || HitWall(pos.x - size.x, pos.y + size.y) || HitWall(pos.x, pos.y + size.y) || HitWall(pos.x + size.x, pos.y + size.y) || HitWall(pos.x + size.x, pos.y) || HitWall(pos.x + size.x, pos.y - size.y) || HitWall(pos.x, pos.y - size.y)) { return true; } return false; }
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); }
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); } }
void EventPoll(EventHandlers *handlers, Uint32 ticks) { SDL_Event e; handlers->HasResolutionChanged = false; handlers->HasLostFocus = false; KeyPrePoll(&handlers->keyboard); MousePrePoll(&handlers->mouse); JoyPrePoll(&handlers->joysticks); SDL_free(handlers->DropFile); handlers->DropFile = NULL; // Don't process mouse events if focus just regained this cycle // This is to prevent bogus click events outside the window, e.g. in the // title bar bool regainedFocus = false; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_KEYDOWN: if (e.key.repeat) { break; } KeyOnKeyDown(&handlers->keyboard, e.key.keysym); break; case SDL_KEYUP: KeyOnKeyUp(&handlers->keyboard, e.key.keysym); break; case SDL_TEXTINPUT: strcpy(handlers->keyboard.Typed, e.text.text); break; case SDL_CONTROLLERDEVICEADDED: { const SDL_JoystickID jid = JoyAdded(e.cdevice.which); if (jid == -1) { break; } // If there are players with unset devices, // set this controller to them CA_FOREACH(PlayerData, p, gPlayerDatas) if (p->inputDevice == INPUT_DEVICE_UNSET) { PlayerTrySetInputDevice(p, INPUT_DEVICE_JOYSTICK, jid); LOG(LM_INPUT, LL_INFO, "Joystick %d assigned to player %d", jid, p->UID); break; } CA_FOREACH_END() } break; case SDL_CONTROLLERDEVICEREMOVED: JoyRemoved(e.cdevice.which); // If there was a player using this joystick, // set their input device to nothing CA_FOREACH(PlayerData, p, gPlayerDatas) if (p->inputDevice == INPUT_DEVICE_JOYSTICK && p->deviceIndex == e.cdevice.which) { PlayerTrySetInputDevice(p, INPUT_DEVICE_UNSET, 0); LOG(LM_INPUT, LL_WARN, "Joystick for player %d removed", p->UID); break; } CA_FOREACH_END() break; case SDL_CONTROLLERBUTTONDOWN: JoyOnButtonDown(e.cbutton); break; case SDL_CONTROLLERBUTTONUP: JoyOnButtonUp(e.cbutton); break; case SDL_CONTROLLERAXISMOTION: JoyOnAxis(e.caxis); break; case SDL_MOUSEBUTTONDOWN: if (regainedFocus) break; MouseOnButtonDown(&handlers->mouse, e.button.button); break; case SDL_MOUSEBUTTONUP: if (regainedFocus) break; MouseOnButtonUp(&handlers->mouse, e.button.button); break; case SDL_MOUSEWHEEL: if (regainedFocus) break; MouseOnWheel(&handlers->mouse, e.wheel.x, e.wheel.y); break; case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_FOCUS_GAINED: regainedFocus = true; MusicSetPlaying(&gSoundDevice, true); break; case SDL_WINDOWEVENT_FOCUS_LOST: if (!gCampaign.IsClient && !ConfigGetBool(&gConfig, "StartServer")) { MusicSetPlaying(&gSoundDevice, false); handlers->HasLostFocus = true; } // Reset input handlers EventReset( handlers, handlers->mouse.cursor, handlers->mouse.trail); break; case SDL_WINDOWEVENT_SIZE_CHANGED: handlers->HasResolutionChanged = true; if (gGraphicsDevice.cachedConfig.IsEditor) { const int scale = ConfigGetInt(&gConfig, "Graphics.ScaleFactor"); GraphicsConfigSet( &gGraphicsDevice.cachedConfig, Vec2iScaleDiv( Vec2iNew(e.window.data1, e.window.data2), scale), false, scale, gGraphicsDevice.cachedConfig.ScaleMode, gGraphicsDevice.cachedConfig.Brightness); GraphicsInitialize(&gGraphicsDevice); } break; default: // do nothing break; } break; case SDL_QUIT: handlers->HasQuit = true; break; case SDL_DROPFILE: handlers->DropFile = e.drop.file; break; default: break; } } KeyPostPoll(&handlers->keyboard, ticks); MousePostPoll(&handlers->mouse, ticks); }
static bool DoDamageCharacter( const Vec2i pos, const Vec2i hitVector, const int power, const int flags, const int player, const int uid, const TTileItem *target, const special_damage_e special, const HitSounds *hitSounds, const bool allowFriendlyHitSound) { // Create events: hit, damage, score TActor *actor = CArrayGet(&gActors, target->id); CASSERT(actor->isInUse, "Cannot damage nonexistent player"); bool canHit = CanHitCharacter(flags, uid, actor); if (canHit) { GameEvent e; e.Type = GAME_EVENT_HIT_CHARACTER; e.u.HitCharacter.TargetId = actor->tileItem.id; e.u.HitCharacter.Special = special; GameEventsEnqueue(&gGameEvents, e); if (gConfig.Sound.Hits && hitSounds != NULL && !ActorIsImmune(actor, special) && (allowFriendlyHitSound || !ActorIsInvulnerable( actor, flags, player, gCampaign.Entry.Mode))) { GameEvent es; es.Type = GAME_EVENT_SOUND_AT; es.u.SoundAt.Sound = hitSounds->Flesh; es.u.SoundAt.Pos = pos; GameEventsEnqueue(&gGameEvents, es); } if (gConfig.Game.ShotsPushback) { GameEvent ei; ei.Type = GAME_EVENT_ACTOR_IMPULSE; ei.u.ActorImpulse.Id = actor->tileItem.id; ei.u.ActorImpulse.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR); GameEventsEnqueue(&gGameEvents, ei); } if (CanDamageCharacter(flags, player, uid, actor, special)) { GameEvent e1; e1.Type = GAME_EVENT_DAMAGE_CHARACTER; e1.u.DamageCharacter.Power = power; e1.u.DamageCharacter.PlayerIndex = player; e1.u.DamageCharacter.TargetId = actor->tileItem.id; e1.u.DamageCharacter.TargetPlayerIndex = -1; if (actor->pData) { e1.u.DamageCharacter.TargetPlayerIndex = actor->pData->playerIndex; } GameEventsEnqueue(&gGameEvents, e1); if (gConfig.Game.Gore != GORE_NONE) { GameEvent eb; memset(&eb, 0, sizeof eb); eb.Type = GAME_EVENT_ADD_PARTICLE; eb.u.AddParticle.FullPos = Vec2iReal2Full(pos); eb.u.AddParticle.Z = 10 * Z_FACTOR; int bloodPower = power * 2; int bloodSize = 1; while (bloodPower > 0) { switch (bloodSize) { case 1: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood1"); break; case 2: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood2"); break; default: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood3"); break; } bloodSize++; if (bloodSize > 3) { bloodSize = 1; } if (gConfig.Game.ShotsPushback) { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, (rand() % 8 + 8) * power), 15 * SHOT_IMPULSE_DIVISOR); } else { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, rand() % 8 + 8), 20); } eb.u.AddParticle.Vel.x += (rand() % 128) - 64; eb.u.AddParticle.Vel.y += (rand() % 128) - 64; eb.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); eb.u.AddParticle.DZ = (rand() % 6) + 6; eb.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, eb); switch (gConfig.Game.Gore) { case GORE_LOW: bloodPower /= 8; break; case GORE_MEDIUM: bloodPower /= 2; break; default: bloodPower = bloodPower * 7 / 8; break; } } } if (player >= 0 && power != 0) { // Calculate score based on // if they hit a penalty character GameEvent e2; e2.Type = GAME_EVENT_SCORE; e2.u.Score.PlayerIndex = player; if (actor->flags & FLAGS_PENALTY) { e2.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e2.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e2); } } } return canHit; }
int PlayerSelection(int numPlayers, GraphicsDevice *graphics) { int i; int hasInputDevice[MAX_PLAYERS]; PlayerSelectMenu menus[MAX_PLAYERS]; for (i = 0; i < numPlayers; i++) { PlayerSelectMenusCreate( &menus[i], numPlayers, i, &gCampaign.Setting.characters.players[i], &gPlayerDatas[i], &gInputDevices, graphics, &gConfig.Input); hasInputDevice[i] = 0; } KeyInit(&gInputDevices.keyboard); for (;;) { int cmds[MAX_PLAYERS]; int isDone = 1; InputPoll(&gInputDevices, SDL_GetTicks()); if (KeyIsPressed(&gInputDevices.keyboard, SDLK_ESCAPE)) { // TODO: destroy menus return 0; // hack to allow exit } GetPlayerCmds(&cmds, gPlayerDatas); for (i = 0; i < numPlayers; i++) { if (hasInputDevice[i] && !MenuIsExit(&menus[i].ms)) { MenuProcessCmd(&menus[i].ms, cmds[i]); } } for (i = 0; i < numPlayers; i++) { if (!MenuIsExit(&menus[i].ms)) { isDone = 0; } } if (isDone) { break; } AssignPlayerInputDevices( hasInputDevice, numPlayers, gPlayerDatas, &gInputDevices, &gConfig.Input); GraphicsBlitBkg(graphics); for (i = 0; i < numPlayers; i++) { if (hasInputDevice[i]) { MenuDisplay(&menus[i].ms); } else { Vec2i center; const char *prompt = "Press Fire to join..."; Vec2i offset = Vec2iScaleDiv(TextGetSize(prompt), -2); int w = graphics->cachedConfig.ResolutionWidth; int h = graphics->cachedConfig.ResolutionHeight; switch (numPlayers) { case 1: // Center of screen center = Vec2iNew(w / 2, h / 2); break; case 2: // Side by side center = Vec2iNew(i * w / 2 + w / 4, h / 2); break; case 3: case 4: // Four corners center = Vec2iNew( (i & 1) * w / 2 + w / 4, (i / 2) * h / 2 + h / 4); break; default: assert(0 && "not implemented"); break; } DrawTextString(prompt, graphics, Vec2iAdd(center, offset)); } } BlitFlip(graphics, &gConfig.Graphics); SDL_Delay(10); } for (i = 0; i < numPlayers; i++) { MenuSystemTerminate(&menus[i].ms); } return 1; }