WIP buy/sell HP/lives #757

This commit is contained in:
Cong
2023-03-15 23:40:05 +11:00
parent 401c83731f
commit 6793cab66c
26 changed files with 404 additions and 339 deletions

View File

@@ -8,5 +8,8 @@
"BuyAndSell": true,
"RandomPickups": false,
"DoorOpenTicks": 0,
"MaxLives": 4,
"PlayerHP": 20,
"PlayerMaxHP": 50,
"Missions": 1
}

View File

@@ -99,22 +99,6 @@
"MapObject": "Chainsaw spawner",
"Positions": [[7, 3]]
},
{
"MapObject": "Chainsaw x2 spawner",
"Positions": [[7, 5]]
},
{
"MapObject": "Chainsaw x3 spawner",
"Positions": [[7, 7]]
},
{
"MapObject": "Chainsaw x4 spawner",
"Positions": [[7, 9]]
},
{
"MapObject": "Chainsaw x5 spawner",
"Positions": [[7, 11]]
},
{
"MapObject": "Barehanded spawner",
"Positions": [[7, 13]]

View File

@@ -36,6 +36,7 @@ set(CDOGS_SDL_SOURCES
prep.c
prep_equip.c
screens_end.c
util_menu.c
weapon_menu.c)
set(CDOGS_SDL_HEADERS
ammo_menu.h
@@ -58,6 +59,7 @@ set(CDOGS_SDL_HEADERS
prep.h
prep_equip.h
screens_end.h
util_menu.h
weapon_menu.h)
set(CDOGS_SDL_EXTRA)
if(WIN32)

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2015, 2018, 2020-2023 Cong Xu
Copyright (c) 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -30,20 +30,18 @@
#include <assert.h>
#include <cdogs/ai_coop.h>
#include <cdogs/draw/draw_actor.h>
#include <cdogs/draw/drawtools.h>
#include <cdogs/draw/nine_slice.h>
#include <cdogs/font.h>
#define AMMO_MENU_WIDTH 80
#define MENU_WIDTH 80
#define EQUIP_MENU_SLOT_HEIGHT 40
#define AMMO_MENU_MAX_ROWS 4
#define AMMO_BUTTON_BG_W 20
#define MENU_MAX_ROWS 4
#define BUTTON_BG_W 20
#define SCROLL_H 12
#define SLOT_BORDER 3
#define AMMO_LEVEL_W 2
#define AMMO_ROW_H 10
#define LEVEL_W 2
#define ROW_H 10
static int GetSelectedAmmo(const AmmoMenu *menu)
{
@@ -135,11 +133,11 @@ static int ClampScroll(const AmmoMenu *menu)
// Update menu scroll based on selected ammo
const int selectedRow = menu->idx / 2;
const int lastRow = (int)menu->ammoIds.size / 2;
int minRow = MAX(0, selectedRow - AMMO_MENU_MAX_ROWS + 1);
int maxRow = MIN(selectedRow, MAX(0, lastRow - AMMO_MENU_MAX_ROWS));
int minRow = MAX(0, selectedRow - MENU_MAX_ROWS + 1);
int maxRow = MIN(selectedRow, MAX(0, lastRow - MENU_MAX_ROWS));
// If the selected row is the last row on screen, and we can still
// scroll down (i.e. show the scroll down button), scroll down
if (selectedRow - menu->scroll == AMMO_MENU_MAX_ROWS - 1 &&
if (selectedRow - menu->scroll == MENU_MAX_ROWS - 1 &&
selectedRow < lastRow)
{
minRow++;
@@ -199,12 +197,12 @@ static void DrawMenu(
CA_FOREACH(const int, ammoId, d->ammoIds)
const Ammo *ammo = AmmoGetById(&gAmmo, *ammoId);
if (_ca_index >= d->scroll && _ca_index < d->scroll + AMMO_MENU_MAX_ROWS)
if (_ca_index >= d->scroll && _ca_index < d->scroll + MENU_MAX_ROWS)
{
DrawAmmoMenuItem(
d, g, _ca_index, ammo, svec2i(pos.x, pos.y + ammoY), bgSize);
}
if (_ca_index == d->scroll + AMMO_MENU_MAX_ROWS)
if (_ca_index == d->scroll + MENU_MAX_ROWS)
{
scrollDown = true;
break;
@@ -241,14 +239,14 @@ static void DrawMenu(
const Rect2i scrollRect = Rect2iNew(
svec2i(
pos.x + 3,
pos.y - 1 + ammoY + bgSize.y * AMMO_MENU_MAX_ROWS - SCROLL_H),
pos.y - 1 + ammoY + bgSize.y * MENU_MAX_ROWS - SCROLL_H),
scrollSize);
PicRender(
gradient, g->gameWindow.renderer,
svec2i(
scrollRect.Pos.x + scrollSize.x / 2,
pos.y - gradient->size.y / 2 + ammoY +
bgSize.y * AMMO_MENU_MAX_ROWS - SCROLL_H -
bgSize.y * MENU_MAX_ROWS - SCROLL_H -
gradient->size.y / 2 + 1),
colorBlack, 0, svec2((float)scrollSize.x, 1), SDL_FLIP_VERTICAL,
Rect2iZero());
@@ -299,7 +297,7 @@ static void DrawAmmoMenuItem(
// With: amount/max coloured rectangle
int x = bgPos.x + 4;
int y = bgPos.y + AMMO_ROW_H * 2;
int y = bgPos.y + ROW_H * 2;
// Ammo amount BG
if (a && a->Max > 0 && ammoAmount > 0)
@@ -312,7 +310,7 @@ static void DrawAmmoMenuItem(
true);
}
y = bgPos.y + AMMO_ROW_H;
y = bgPos.y + ROW_H;
// Sell/buy buttons
if (a != NULL)
@@ -321,30 +319,30 @@ static void DrawAmmoMenuItem(
selected && data->Active && (data->idx & 1) == 1;
const bool canSell = CanSell(data, ammoId);
const FontOpts foptsSell = {
ALIGN_CENTER, ALIGN_START, svec2i(AMMO_BUTTON_BG_W, FontH()),
ALIGN_CENTER, ALIGN_START, svec2i(BUTTON_BG_W, FontH()),
svec2i(2, 2), sellSelected ? colorRed : colorGray};
x = bgPos.x + bgSize.x - AMMO_BUTTON_BG_W - 2;
x = bgPos.x + bgSize.x - BUTTON_BG_W - 2;
const struct vec2i sellPos = svec2i(x, y);
Draw9Slice(
g, data->buttonBG,
Rect2iNew(sellPos, svec2i(AMMO_BUTTON_BG_W, FontH() + 4)), 3, 3, 3,
3, true,
Rect2iNew(sellPos, svec2i(BUTTON_BG_W, FontH() + 4)), 3, 3, 3, 3,
true,
canSell ? (sellSelected ? colorRed : colorMaroon) : colorGray,
SDL_FLIP_NONE);
FontStrOpt("Sell", sellPos, foptsSell);
x -= AMMO_BUTTON_BG_W + 3;
x -= BUTTON_BG_W + 3;
const bool buySelected =
selected && data->Active && (data->idx & 1) == 0;
const bool canBuy = CanBuy(data, ammoId);
const FontOpts foptsBuy = {
ALIGN_CENTER, ALIGN_START, svec2i(AMMO_BUTTON_BG_W, FontH()),
ALIGN_CENTER, ALIGN_START, svec2i(BUTTON_BG_W, FontH()),
svec2i(2, 2), buySelected ? colorGreen : colorGray};
const struct vec2i buyPos = svec2i(x, y);
Draw9Slice(
g, data->buttonBG,
Rect2iNew(buyPos, svec2i(AMMO_BUTTON_BG_W, FontH() + 4)), 3, 3, 3,
3, true,
Rect2iNew(buyPos, svec2i(BUTTON_BG_W, FontH() + 4)), 3, 3, 3, 3,
true,
canBuy ? (buySelected ? colorGreen : colorOfficeGreen) : colorGray,
SDL_FLIP_NONE);
FontStrOpt("Buy", buyPos, foptsBuy);
@@ -370,7 +368,7 @@ static void DrawAmmoMenuItem(
FontStrOpt(buf, svec2i(x, y), foptsP);
}
y += AMMO_ROW_H * 2;
y += ROW_H * 2;
x = bgPos.x + 4;
// Ammo amount BG
@@ -401,7 +399,7 @@ static void DrawAmmoMenuItem(
FontStrOpt(buf, svec2i(x, y), foptsA);
}
y -= AMMO_ROW_H;
y -= ROW_H;
// Icon
if (a != NULL)
@@ -486,7 +484,7 @@ void AmmoMenuReset(AmmoMenu *menu)
}
CArrayPushBack(&menu->ammoIds, &i);
}
menu->idx = CLAMP(menu->idx, 0, (int)menu->ammoIds.size);
menu->idx = CLAMP(menu->idx, 0, (int)menu->ammoIds.size * 2);
}
void AmmoMenuActivate(AmmoMenu *menu)

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2016, 2019, 2021-2022 Cong Xu
Copyright (c) 2013-2016, 2019, 2021-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -301,7 +301,7 @@ void AutosaveSave(Autosave *autosave, const char *filename)
json_tree_to_string(root, &text);
char *formatText = json_format_string(text);
FILE *f = fopen(filename, "w");
if (f == NULL)
{
@@ -440,8 +440,8 @@ const CampaignSave *AutosaveGetLastCampaign(const Autosave *a)
void PlayerSavesApply(const CArray *playerSaves, const bool weaponPersist)
{
for (int i = 0, idx = 0; i < (int)gPlayerDatas.size &&
idx < (int)playerSaves->size;
for (int i = 0, idx = 0;
i < (int)gPlayerDatas.size && idx < (int)playerSaves->size;
i++, idx++)
{
PlayerData *p = CArrayGet(&gPlayerDatas, i);
@@ -465,7 +465,7 @@ void PlayerSavesApply(const CArray *playerSaves, const bool weaponPersist)
}
if (ps->Lives > 0)
{
p->Lives = ps->Lives;
PlayerSetLives(p, ps->Lives);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2013-2022 Cong Xu
Copyright (c) 2013-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -60,7 +60,8 @@ static void CampaignIntroOnExit(GameLoopData *data);
static void CampaignIntroInput(GameLoopData *data);
static GameLoopResult CampaignIntroUpdate(GameLoopData *data, LoopRunner *l);
static void CampaignIntroDraw(GameLoopData *data);
GameLoopData *ScreenCampaignIntro(CampaignSetting *c, const GameMode gameMode, const CampaignEntry *entry)
GameLoopData *ScreenCampaignIntro(
CampaignSetting *c, const GameMode gameMode, const CampaignEntry *entry)
{
ScreenCampaignIntroData *data;
CMALLOC(data, sizeof *data);
@@ -89,7 +90,8 @@ static void CampaignIntroOnEnter(GameLoopData *data)
else
{
MusicPlayFromChunk(
&gSoundDevice.music, MUSIC_MENU, &sData->c->CustomSongs[MUSIC_MENU]);
&gSoundDevice.music, MUSIC_MENU,
&sData->c->CustomSongs[MUSIC_MENU]);
}
}
static void CampaignIntroOnExit(GameLoopData *data)
@@ -117,14 +119,16 @@ static GameLoopResult CampaignIntroUpdate(GameLoopData *data, LoopRunner *l)
{
LoopRunnerPop(l);
}
else if (!IsIntroNeeded(gCampaign.Entry.Mode) ||
else if (
!IsIntroNeeded(gCampaign.Entry.Mode) ||
sData->waitResult == EVENT_WAIT_OK)
{
// Switch to num players selection
LoopRunnerPush(
l, ScreenLoading(
"Loading campaign...", true,
NumPlayersSelection(&gGraphicsDevice, &gEventHandlers), true));
l,
ScreenLoading(
"Loading campaign...", true,
NumPlayersSelection(&gGraphicsDevice, &gEventHandlers), true));
}
return UPDATE_RESULT_OK;
}
@@ -691,8 +695,8 @@ static void ApplyBonuses(PlayerData *p, const int bonus)
}
static int GetHealthBonus(const PlayerData *p)
{
const int maxHealth = ModeMaxHealth(gCampaign.Entry.Mode);
return p->hp > maxHealth - 50 ? (p->hp + 50 - maxHealth) * 10 : 0;
const int maxHealth = p->Char.maxHealth;
return p->hp > maxHealth - 50 ? (p->hp + 50 - p->Char.maxHealth) * 10 : 0;
}
static int GetResurrectionFee(const PlayerData *p)
{

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2015, 2017-2018, 2020-2022 Cong Xu
Copyright (c) 2013-2015, 2017-2018, 2020-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -509,9 +509,11 @@ static bool TryCompleteNearbyObjective(
CA_FOREACH_END()
return false;
}
static bool ShouldPickupGun(const PickupEffect *pe, const TActor *actor,
const TActor *closestPlayer);
static AIObjectiveType GetPickupObjectiveType(const Pickup *p, int *uid, const TActor *actor, const TActor *closestPlayer)
static bool ShouldPickupGun(
const PickupEffect *pe, const TActor *actor, const TActor *closestPlayer);
static AIObjectiveType GetPickupObjectiveType(
const Pickup *p, int *uid, const TActor *actor,
const TActor *closestPlayer)
{
CA_FOREACH(const PickupEffect, pe, p->class->Effects)
switch (pe->Type)
@@ -523,7 +525,7 @@ static AIObjectiveType GetPickupObjectiveType(const Pickup *p, int *uid, const T
return AI_OBJECTIVE_TYPE_PICKUP;
case PICKUP_HEALTH:
// Pick up if we are on low health, and lower than lead player
if (actor->health > ModeMaxHealth(gCampaign.Entry.Mode) / 4 ||
if (actor->health > CampaignGetMaxHP(&gCampaign) / 4 ||
(closestPlayer != NULL && actor->health >= closestPlayer->health))
{
continue;
@@ -557,7 +559,9 @@ static AIObjectiveType GetPickupObjectiveType(const Pickup *p, int *uid, const T
return AI_OBJECTIVE_TYPE_PICKUP;
case PICKUP_LIVES:
// Pick up if we have less lives than lead player
if (closestPlayer != NULL && PlayerDataGetByUID(actor->PlayerUID)->Lives >= PlayerDataGetByUID(closestPlayer->PlayerUID)->Lives)
if (closestPlayer != NULL &&
PlayerDataGetByUID(actor->PlayerUID)->Lives >=
PlayerDataGetByUID(closestPlayer->PlayerUID)->Lives)
{
continue;
}
@@ -713,8 +717,8 @@ static void FindObjectivesSortedByDistance(
objectives->data, objectives->size, objectives->elemSize,
CompareClosestObjective);
}
static bool ShouldPickupGun(const PickupEffect *pe, const TActor *actor,
const TActor *closestPlayer)
static bool ShouldPickupGun(
const PickupEffect *pe, const TActor *actor, const TActor *closestPlayer)
{
if (!gCampaign.Setting.Ammo)
{

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2018, 2020-2022 Cong Xu
Copyright (c) 2013-2018, 2020-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -102,6 +102,37 @@ void CampaignSettingTerminateAll(CampaignSetting *setting)
MapObjectsClear(&gMapObjects.CustomClasses);
}
int CampaignGetMaxLives(const Campaign *c)
{
switch (c->Entry.Mode)
{
case GAME_MODE_DOGFIGHT:
return 1;
case GAME_MODE_DEATHMATCH:
return ConfigGetInt(&gConfig, "Deathmatch.Lives");
default:
if (c->Setting.MaxLives > 0)
{
return c->Setting.MaxLives;
}
return ConfigGetInt(&gConfig, "Game.Lives");
}
}
int CampaignGetMaxHP(const Campaign *c)
{
switch (c->Entry.Mode)
{
case GAME_MODE_DOGFIGHT:
return 500 * ConfigGetInt(&gConfig, "Dogfight.PlayerHP") / 100;
default:
if (c->Setting.PlayerMaxHP > 0)
{
return c->Setting.PlayerMaxHP;
}
return 200 * ConfigGetInt(&gConfig, "Game.PlayerHP") / 100;
}
}
bool CampaignListIsEmpty(const CampaignList *c)
{
return c->list.size == 0 && c->subFolders.size == 0;

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2014, 2016-2017, 2020-2022 Cong Xu
Copyright (c) 2013-2014, 2016-2017, 2020-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -58,6 +58,9 @@ typedef struct
bool BuyAndSell;
bool RandomPickups;
int DoorOpenTicks;
int MaxLives;
int PlayerHP;
int PlayerMaxHP;
CArray Missions; // of Mission
CharacterStore characters;
MusicChunk CustomSongs[MUSIC_COUNT];
@@ -87,6 +90,9 @@ void CampaignSettingInit(CampaignSetting *setting);
void CampaignSettingTerminate(CampaignSetting *c);
void CampaignSettingTerminateAll(CampaignSetting *setting);
int CampaignGetMaxLives(const Campaign *c);
int CampaignGetMaxHP(const Campaign *c);
bool CampaignListIsEmpty(const CampaignList *c);
void LoadAllCampaigns(CustomCampaigns *campaigns);

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2014, 2016, 2019-2021 Cong Xu
Copyright (c) 2013-2014, 2016, 2019-2021, 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -237,7 +237,8 @@ bool CharacterSave(CharacterStore *s, const char *path)
AddIntPair(node, "flags", c->flags);
if (c->Drop != NULL)
{
json_insert_pair_into_object(node, "Drop", json_new_string(c->Drop->Name));
json_insert_pair_into_object(
node, "Drop", json_new_string(c->Drop->Name));
}
AddIntPair(node, "probabilityToMove", c->bot->probabilityToMove);
AddIntPair(node, "probabilityToTrack", c->bot->probabilityToTrack);
@@ -341,7 +342,7 @@ int CharacterGetStartingHealth(const Character *c, const bool isNPC)
}
else
{
return c->maxHealth;
return c->hp > 0 ? c->hp : c->maxHealth;
}
}

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2014, 2016, 2019-2021 Cong Xu
Copyright (c) 2013-2014, 2016, 2019-2021, 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -50,6 +50,7 @@ typedef struct
float speed;
const WeaponClass *Gun;
int maxHealth;
int hp;
unsigned int flags;
CharColors Colors;
const PickupClass *Drop;

View File

@@ -94,8 +94,8 @@ bool CanLevelSelect(const GameMode mode)
bool IsMissionBriefingNeeded(const GameMode mode, const char *missionBriefing)
{
return HasObjectives(mode) && GetNumPlayers(PLAYER_ANY, false, true) > 0 && missionBriefing != NULL &&
strlen(missionBriefing) > 0;
return HasObjectives(mode) && GetNumPlayers(PLAYER_ANY, false, true) > 0 &&
missionBriefing != NULL && strlen(missionBriefing) > 0;
}
bool AreKeysAllowed(const GameMode mode)
@@ -143,30 +143,6 @@ int ModeMaxRoundsWon(const GameMode mode)
}
}
int ModeLives(const GameMode mode)
{
switch (mode)
{
case GAME_MODE_DOGFIGHT:
return 1;
case GAME_MODE_DEATHMATCH:
return ConfigGetInt(&gConfig, "Deathmatch.Lives");
default:
return ConfigGetInt(&gConfig, "Game.Lives");
}
}
int ModeMaxHealth(const GameMode mode)
{
switch (mode)
{
case GAME_MODE_DOGFIGHT:
return 500 * ConfigGetInt(&gConfig, "Dogfight.PlayerHP") / 100;
default:
return 200 * ConfigGetInt(&gConfig, "Game.PlayerHP") / 100;
}
}
bool ModeHasNPCs(const GameMode mode)
{
return mode != GAME_MODE_DOGFIGHT && mode != GAME_MODE_DEATHMATCH;

View File

@@ -52,6 +52,4 @@ bool IsPVP(const GameMode mode);
bool HasExit(const GameMode mode);
bool HasRounds(const GameMode mode);
int ModeMaxRoundsWon(const GameMode mode);
int ModeLives(const GameMode mode);
int ModeMaxHealth(const GameMode mode);
bool ModeHasNPCs(const GameMode mode);

View File

@@ -307,6 +307,9 @@ int MapArchiveSave(const char *filename, CampaignSetting *c)
AddBoolPair(root, "BuyAndSell", c->BuyAndSell);
AddBoolPair(root, "RandomPickups", c->RandomPickups);
AddIntPair(root, "DoorOpenTicks", c->DoorOpenTicks);
AddIntPair(root, "MaxLives", c->MaxLives);
AddIntPair(root, "PlayerHP", c->PlayerHP);
AddIntPair(root, "PlayerMaxHP", c->PlayerMaxHP);
AddIntPair(root, "Missions", (int)c->Missions.size);
char buf2[CDOGS_PATH_MAX];
sprintf(buf2, "%s/campaign.json", buf);

View File

@@ -169,6 +169,9 @@ void MapNewLoadCampaignJSON(json_t *root, CampaignSetting *c)
// Default one second door close
c->DoorOpenTicks = FPS_FRAMELIMIT;
LoadInt(&c->DoorOpenTicks, root, "DoorOpenTicks");
LoadInt(&c->MaxLives, root, "MaxLives");
LoadInt(&c->PlayerHP, root, "PlayerHP");
LoadInt(&c->PlayerMaxHP, root, "PlayerMaxHP");
}
static void LoadMissionObjectives(

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2017, 2021 Cong Xu
Copyright (c) 2014-2017, 2021, 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -419,8 +419,8 @@ void NetServerSendGameStartMessages(NetServer *n, const int peerId)
CA_FOREACH(const PlayerData, pOther, gPlayerDatas)
NPlayerData pd = NMakePlayerData(pOther);
NetServerSendMsg(n, peerId, GAME_EVENT_PLAYER_DATA, &pd);
LOG(LM_NET, LL_DEBUG, "send player data uid(%d) maxHealth(%d)",
(int)pd.UID, (int)pd.MaxHealth);
LOG(LM_NET, LL_DEBUG, "send player data uid(%d) maxHealth(%d) HP(%d)",
(int)pd.UID, (int)pd.MaxHealth, (int)pd.HP);
CA_FOREACH_END()
// Send all game-specific config values

View File

@@ -77,6 +77,7 @@ NPlayerData NMakePlayerData(const PlayerData *p)
d.Stats = p->Stats;
d.Totals = p->Totals;
d.MaxHealth = p->Char.maxHealth;
d.HP = p->hp;
d.LastMission = p->lastMission;
d.UID = p->UID;
Ammo2Net(&d.Ammo_count, d.Ammo, &p->ammo);

View File

@@ -99,13 +99,14 @@ void PlayerDataAddOrUpdate(const NPlayerData pd)
p->Stats = pd.Stats;
p->Totals = pd.Totals;
p->Char.maxHealth = pd.MaxHealth;
p->Char.hp = pd.HP;
p->lastMission = pd.LastMission;
// Ready players as well
p->Ready = true;
LOG(LM_MAIN, LL_INFO, "update player UID(%d) maxHealth(%d)", p->UID,
p->Char.maxHealth);
LOG(LM_MAIN, LL_INFO, "update player UID(%d) maxHealth(%d) HP(%d)", p->UID,
p->Char.maxHealth, p->Char.hp);
}
static void PlayerTerminate(PlayerData *p);
@@ -236,12 +237,12 @@ NPlayerData PlayerDataDefault(const int idx)
NPlayerData PlayerDataMissionReset(const PlayerData *p)
{
NPlayerData pd = NMakePlayerData(p);
pd.Lives = ModeLives(gCampaign.Entry.Mode);
pd.Lives = CampaignGetMaxLives(&gCampaign);
memset(&pd.Stats, 0, sizeof pd.Stats);
pd.LastMission = gCampaign.MissionIndex;
pd.MaxHealth = ModeMaxHealth(gCampaign.Entry.Mode);
pd.MaxHealth = CampaignGetMaxHP(&gCampaign);
return pd;
}
@@ -566,7 +567,9 @@ void PlayerAddWeaponToSlot(
{
continue;
}
if (p->guns[i] && (p->guns[i] == wc || WeaponClassGetPrerequisite(p->guns[i]) == wc || p->guns[i] == WeaponClassGetPrerequisite(wc)))
if (p->guns[i] && (p->guns[i] == wc ||
WeaponClassGetPrerequisite(p->guns[i]) == wc ||
p->guns[i] == WeaponClassGetPrerequisite(wc)))
{
p->guns[i] = p->guns[slot];
PlayerAddWeaponToSlot(p, wc, i);
@@ -710,3 +713,13 @@ void PlayerAddAmmo(
PlayerScore(p, dLots * a->Price);
}
}
void PlayerSetHP(PlayerData *p, const int hp)
{
p->hp = CLAMP(hp, 1, CampaignGetMaxHP(&gCampaign));
}
void PlayerSetLives(PlayerData *p, const int lives)
{
p->Lives = CLAMP(lives, 1, CampaignGetMaxLives(&gCampaign));
}

View File

@@ -47,13 +47,13 @@ typedef struct
const WeaponClass *guns[MAX_WEAPONS];
CArray ammo; // of int
int Lives;
int hp;
NPlayerStats Stats;
NPlayerStats Totals;
// Used for end-of-game score tallying
int survived;
int hp;
int missions;
int lastMission;
int allTime, today;
@@ -119,4 +119,7 @@ void PlayerAddMinimalWeapons(PlayerData *p);
bool PlayerUsesAmmo(const PlayerData *p, const int ammoId);
bool PlayerUsesAnyAmmo(const PlayerData *p);
int PlayerGetAmmoAmount(const PlayerData *p, const int ammoId);
void PlayerAddAmmo(PlayerData *p, const int ammoId, const int amount, const bool isFree);
void PlayerAddAmmo(
PlayerData *p, const int ammoId, const int amount, const bool isFree);
void PlayerSetHP(PlayerData *p, const int hp);
void PlayerSetLives(PlayerData *p, const int lives);

View File

@@ -146,7 +146,7 @@ static double HealthScale(void *data)
UNUSED(data);
// Update time until next spawn based on:
// Damage taken (find player with lowest health)
int minHealth = ModeMaxHealth(gCampaign.Entry.Mode);
int minHealth = CampaignGetMaxHP(&gCampaign);
int maxHealth = minHealth;
CA_FOREACH(const PlayerData, p, gPlayerDatas)
if (!IsPlayerAlive(p))

View File

@@ -1,7 +1,7 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2020-2021 Cong Xu
Copyright (c) 2020-2021, 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -143,6 +143,30 @@ static bool Draw(SDL_Window *win, struct nk_context *ctx, void *data)
{
changed = true;
}
if (DrawNumberSlider(
ctx, "Max lives", "(0 = use game option)", 0, 5, 1,
&cData->c->Setting.MaxLives))
{
changed = true;
}
if (DrawNumberSlider(
ctx, "Player HP", "Starting player HP (0 = use max HP)", 0,
1000, 10, &cData->c->Setting.PlayerHP))
{
changed = true;
}
if (DrawNumberSlider(
ctx, "Player max HP", "(0 = use game option)", 0, 1000, 5,
&cData->c->Setting.PlayerMaxHP))
{
changed = true;
}
// Clamp PlayerHP within PlayerMaxHP
if (changed && cData->c->Setting.PlayerMaxHP > 0)
{
cData->c->Setting.PlayerHP =
MIN(cData->c->Setting.PlayerHP, cData->c->Setting.PlayerMaxHP);
}
nk_end(ctx);
}
if (changed)

View File

@@ -40,6 +40,7 @@
#define NO_GUN_LABEL "(None)"
#define AMMO_LABEL "Ammo..."
#define UTIL_LABEL "Utilities..."
#define END_MENU_LABEL "(End)"
#define EQUIP_MENU_WIDTH 80
#define EQUIP_MENU_SLOT_HEIGHT 40
@@ -99,7 +100,10 @@ static bool IsSlotDisabled(const EquipMenu *data, const int slot)
{
return !PlayerUsesAnyAmmo(pData);
}
// TODO: util menus
if (slot == data->utilSlot)
{
return false;
}
return false;
}
@@ -174,7 +178,8 @@ static void DrawEquipSlot(
svec2i(slotSize.x, slotSize.y - 2 * FontH() - 1));
const Pic *arrow = PicManagerGetPic(&gPicManager, "hud/arrow");
const struct vec2i metaPos = svec2i(pos.x + EQUIP_MENU_WIDTH / 2 - 6, y + 13);
const struct vec2i metaPos =
svec2i(pos.x + EQUIP_MENU_WIDTH / 2 - 6, y + 13);
const struct vec2i arrowPos = svec2i(metaPos.x - 4, metaPos.y);
switch (data->SlotMeta[slot])
{
@@ -184,10 +189,14 @@ static void DrawEquipSlot(
DrawCross(g, metaPos, colorGreen);
break;
case META_UPGRADE:
PicRender(arrow, g->gameWindow.renderer, arrowPos, colorGreen, 0, svec2_one(), SDL_FLIP_NONE, Rect2iZero());
PicRender(
arrow, g->gameWindow.renderer, arrowPos, colorGreen, 0,
svec2_one(), SDL_FLIP_NONE, Rect2iZero());
break;
case META_DOWNGRADE:
PicRender(arrow, g->gameWindow.renderer, arrowPos, colorRed, 0, svec2_one(), SDL_FLIP_VERTICAL, Rect2iZero());
PicRender(
arrow, g->gameWindow.renderer, arrowPos, colorRed, 0, svec2_one(),
SDL_FLIP_VERTICAL, Rect2iZero());
break;
default:
CASSERT(false, "unexpected");
@@ -244,6 +253,10 @@ static void DrawEquipMenu(
{
costDiff = AmmoMenuSelectedCostDiff(&d->ammoMenu);
}
else if (d->slot == d->utilSlot)
{
costDiff = UtilMenuSelectedCostDiff(&d->utilMenu);
}
if (costDiff != 0)
{
// Draw price diff
@@ -277,6 +290,17 @@ static void DrawEquipMenu(
AMMO_LABEL, selected, disabled, colorWhite);
y += UTIL_MENU_SLOT_HEIGHT;
}
if (d->utilSlot >= 0)
{
const bool selected = d->slot == d->utilSlot;
DisplayMenuItem(
g,
Rect2iNew(
svec2i(CENTER_X(pos, size, FontStrSize(UTIL_LABEL).x), y),
FontStrSize(UTIL_LABEL)),
UTIL_LABEL, selected, false, colorWhite);
y += UTIL_MENU_SLOT_HEIGHT;
}
y += 8;
const WeaponClass *gun = NULL;
@@ -442,7 +466,7 @@ static bool HasWeapon(const CArray *weapons, const WeaponClass *wc);
static void ResetSlotMeta(EquipMenu *menu)
{
const PlayerData *pData = PlayerDataGetByUID(menu->PlayerUID);
// Update SlotMeta
for (int i = 0; i < MAX_WEAPONS; i++)
{
@@ -453,7 +477,8 @@ static void ResetSlotMeta(EquipMenu *menu)
{
continue;
}
const bool isNew = *(const bool *)CArrayGet(&menu->weaponIsNew, _ca_index);
const bool isNew =
*(const bool *)CArrayGet(&menu->weaponIsNew, _ca_index);
DrawGunMeta meta = isNew ? META_NEW : META_NONE;
if (slotWC != NULL)
{
@@ -539,6 +564,7 @@ void EquipMenuCreate(
// Check how many util menu items there are
menu->endSlot = MAX_WEAPONS;
menu->ammoSlot = -1;
menu->utilSlot = -1;
if (gCampaign.Setting.BuyAndSell)
{
if (gCampaign.Setting.Ammo)
@@ -555,6 +581,9 @@ void EquipMenuCreate(
}
}
}
// Can always buy lives; buying HP depends on game settings
menu->utilSlot = menu->endSlot;
menu->endSlot++;
}
// Create equipped weapons menu
@@ -618,6 +647,8 @@ void EquipMenuCreate(
}
AmmoMenuCreate(
&menu->ammoMenu, playerUID, pos, menu->size, handlers, graphics);
UtilMenuCreate(
&menu->utilMenu, playerUID, pos, menu->size, handlers, graphics);
}
}
@@ -628,6 +659,7 @@ void EquipMenuTerminate(EquipMenu *menu)
WeaponMenuTerminate(&menu->weaponMenus[i]);
}
AmmoMenuTerminate(&menu->ammoMenu);
UtilMenuTerminate(&menu->utilMenu);
MenuSystemTerminate(&menu->ms);
CArrayTerminate(&menu->weapons);
CArrayTerminate(&menu->weaponIsNew);
@@ -661,6 +693,14 @@ void EquipMenuUpdate(EquipMenu *menu, const int cmd)
}
menu->equipping = menu->ammoMenu.Active;
}
else if (menu->slot == menu->utilSlot && menu->utilMenu.Active)
{
if (UtilMenuUpdate(&menu->utilMenu, cmd))
{
AnimatedCounterReset(&menu->Cash, p->Totals.Score);
}
menu->equipping = menu->utilMenu.Active;
}
else
{
MenuProcessCmd(&menu->ms, cmd);
@@ -678,6 +718,10 @@ void EquipMenuUpdate(EquipMenu *menu, const int cmd)
{
AmmoMenuActivate(&menu->ammoMenu);
}
else if (menu->slot == menu->utilSlot)
{
UtilMenuActivate(&menu->utilMenu);
}
else
{
CASSERT(false, "unknown menu slot");
@@ -712,4 +756,8 @@ void EquipMenuDraw(const EquipMenu *menu)
{
AmmoMenuDraw(&menu->ammoMenu);
}
else if (menu->slot == menu->utilSlot)
{
UtilMenuDraw(&menu->utilMenu);
}
}

View File

@@ -32,6 +32,7 @@
#include "animated_counter.h"
#include "menu.h"
#include "menu_utils.h"
#include "util_menu.h"
#include "weapon_menu.h"
typedef enum
@@ -55,10 +56,12 @@ typedef struct
DrawGunMeta SlotMeta[MAX_WEAPONS];
struct vec2i size;
int ammoSlot;
int utilSlot;
int endSlot;
MenuSystem ms;
WeaponMenu weaponMenus[MAX_WEAPONS];
AmmoMenu ammoMenu;
UtilMenu utilMenu;
} EquipMenu;
void EquipMenuCreate(

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2018, 2020-2022 Cong Xu
Copyright (c) 2013-2018, 2020-2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -575,8 +575,14 @@ GameLoopData *GameOptions(const GameMode gm)
I("Game.Difficulty");
I("Game.EnemyDensity");
I("Game.NonPlayerHP");
I("Game.PlayerHP");
I("Game.Lives");
if (gCampaign.Setting.PlayerMaxHP == 0)
{
I("Game.PlayerHP");
}
if (gCampaign.Setting.MaxLives == 0)
{
I("Game.Lives");
}
I("Game.HealthPickups");
I("Game.RandomSeed");
MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
@@ -592,7 +598,10 @@ GameLoopData *GameOptions(const GameMode gm)
I("StartServer");
break;
case GAME_MODE_DEATHMATCH:
I("Game.PlayerHP");
if (gCampaign.Setting.PlayerMaxHP == 0)
{
I("Game.PlayerHP");
}
I("Deathmatch.Lives");
I("Game.HealthPickups");
I("Game.RandomSeed");

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2015, 2018, 2020-2023 Cong Xu
Copyright (c) 2023 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -30,90 +30,120 @@
#include <assert.h>
#include <cdogs/draw/draw_actor.h>
#include <cdogs/draw/drawtools.h>
#include <cdogs/draw/nine_slice.h>
#include <cdogs/font.h>
#define AMMO_MENU_WIDTH 80
#define MENU_WIDTH 80
#define EQUIP_MENU_SLOT_HEIGHT 40
#define AMMO_MENU_MAX_ROWS 4
#define AMMO_BUTTON_BG_W 20
#define MENU_MAX_ROWS 4
#define BUTTON_BG_W 20
#define SCROLL_H 12
#define SLOT_BORDER 3
#define AMMO_LEVEL_W 2
#define AMMO_ROW_H 10
#define LEVEL_W 2
#define ROW_H 10
#define HP_DELTA 5
#define HP_PRICE 2000
#define LIFE_PRICE 4000
static int GetSelectedAmmo(const AmmoMenu *menu)
typedef enum
{
if (menu->idx / 2 >= (int)menu->ammoIds.size)
{
return -1;
}
return *(int *)CArrayGet(&menu->ammoIds, menu->idx / 2);
}
OPTION_HP,
OPTION_LIVES,
OPTION_COUNT
} Option;
static bool CanBuy(const AmmoMenu *menu, const int ammoId)
static Option GetSelectedOption(const UtilMenu *menu)
{
const PlayerData *pData = PlayerDataGetByUID(menu->PlayerUID);
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int amount = PlayerGetAmmoAmount(pData, ammoId);
return ammo->Max > amount && ammo->Price <= pData->Totals.Score;
}
static bool CanSell(const AmmoMenu *menu, const int ammoId)
{
const PlayerData *pData = PlayerDataGetByUID(menu->PlayerUID);
const int amount = PlayerGetAmmoAmount(pData, ammoId);
return amount > 0;
return MIN(menu->idx / 2, OPTION_COUNT);
}
int UtilMenuSelectedCostDiff(const UtilMenu *menu)
{
const PlayerData *pData = PlayerDataGetByUID(menu->PlayerUID);
const int ammoId = GetSelectedAmmo(menu);
if (ammoId >= 0)
const Option option = GetSelectedOption(menu);
if (option < OPTION_COUNT)
{
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int amount = PlayerGetAmmoAmount(pData, ammoId);
const bool buy = (menu->idx & 1) == 0;
if (buy && ammo->Max > amount)
int amount = 0;
int max = 0;
int price = 0;
switch (option)
{
return ammo->Price;
case OPTION_HP:
amount = pData->hp;
max = CampaignGetMaxHP(&gCampaign);
price = HP_PRICE;
break;
case OPTION_LIVES:
amount = pData->Lives;
max = CampaignGetMaxLives(&gCampaign);
price = LIFE_PRICE;
break;
default:
CASSERT(false, "unknown option");
break;
}
const bool buy = (menu->idx & 1) == 0;
if (buy && max > amount)
{
return price;
}
else if (amount > 0)
{
return -ammo->Price;
return -price;
}
}
return 0;
}
static void AmmoSelect(menu_t *menu, int cmd, void *data)
static void OnSelect(menu_t *menu, int cmd, void *data)
{
UNUSED(menu);
AmmoMenu *d = data;
UtilMenu *d = data;
const PlayerData *pData = PlayerDataGetByUID(d->PlayerUID);
d->SelectResult = AMMO_MENU_NONE;
d->SelectResult = UTIL_MENU_NONE;
if (cmd & CMD_BUTTON1)
{
const int ammoId = GetSelectedAmmo(d);
if (ammoId < 0)
const Option option = GetSelectedOption(d);
const bool buy = (d->idx & 1) == 0;
int amount = 0;
int max = 0;
int price = 0;
const char *sound = NULL;
switch (option)
{
d->SelectResult = AMMO_MENU_CANCEL;
case OPTION_HP:
amount = pData->hp;
max = CampaignGetMaxHP(&gCampaign);
price = HP_PRICE;
sound = "health";
break;
case OPTION_LIVES:
amount = pData->Lives;
max = CampaignGetMaxLives(&gCampaign);
price = LIFE_PRICE;
sound = "spawn";
break;
case OPTION_COUNT:
d->SelectResult = UTIL_MENU_CANCEL;
MenuPlaySound(MENU_SOUND_BACK);
break;
default:
CASSERT(false, "unknown option");
break;
}
else
{
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const bool buy = (d->idx & 1) == 0;
if (buy && CanBuy(d, ammoId))
if (buy && amount < max && price <= pData->Totals.Score)
{
SoundPlay(&gSoundDevice, StrSound(ammo->Sound));
d->SelectResult = AMMO_MENU_SELECT;
SoundPlay(&gSoundDevice, StrSound(sound));
d->SelectResult = UTIL_MENU_SELECT;
}
else if (!buy && CanSell(d, ammoId))
else if (!buy && amount > 0)
{
SoundPlay(&gSoundDevice, StrSound(ammo->Sound));
d->SelectResult = AMMO_MENU_SELECT;
SoundPlay(&gSoundDevice, StrSound(sound));
d->SelectResult = UTIL_MENU_SELECT;
}
else
{
@@ -123,33 +153,12 @@ static void AmmoSelect(menu_t *menu, int cmd, void *data)
}
else if (cmd & CMD_BUTTON2)
{
d->SelectResult = AMMO_MENU_CANCEL;
d->SelectResult = UTIL_MENU_CANCEL;
MenuPlaySound(MENU_SOUND_BACK);
}
}
static int ClampScroll(const AmmoMenu *menu)
{
// Update menu scroll based on selected ammo
const int selectedRow = menu->idx / 2;
const int lastRow = (int)menu->ammoIds.size / 2;
int minRow = MAX(0, selectedRow - AMMO_MENU_MAX_ROWS + 1);
int maxRow = MIN(selectedRow, MAX(0, lastRow - AMMO_MENU_MAX_ROWS));
// If the selected row is the last row on screen, and we can still
// scroll down (i.e. show the scroll down button), scroll down
if (selectedRow - menu->scroll == AMMO_MENU_MAX_ROWS - 1 &&
selectedRow < lastRow)
{
minRow++;
}
else if (selectedRow == menu->scroll && selectedRow > 0)
{
maxRow--;
}
return CLAMP(menu->scroll, minRow, maxRow);
}
static menu_t *CreateMenu(AmmoMenu *data);
static menu_t *CreateMenu(UtilMenu *data);
void UtilMenuCreate(
UtilMenu *menu, const int playerUID, const struct vec2i pos,
const struct vec2i size, EventHandlers *handlers, GraphicsDevice *graphics)
@@ -169,120 +178,72 @@ static void DrawMenu(
const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
const struct vec2i size, const void *data);
static int HandleInputMenu(int cmd, void *data);
static menu_t *CreateMenu(AmmoMenu *data)
static menu_t *CreateMenu(UtilMenu *data)
{
menu_t *menu = MenuCreateCustom("", DrawMenu, HandleInputMenu, data);
MenuSetPostInputFunc(menu, AmmoSelect, data);
MenuSetPostInputFunc(menu, OnSelect, data);
return menu;
}
static void DrawAmmoMenuItem(
const AmmoMenu *data, GraphicsDevice *g, const int idx, const Ammo *a,
static void DrawUtilMenuItem(
const UtilMenu *data, GraphicsDevice *g, const int idx,
const struct vec2i pos, const struct vec2i bgSize);
static void DrawMenu(
const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
const struct vec2i size, const void *data)
{
UNUSED(menu);
const AmmoMenu *d = data;
const UtilMenu *d = data;
const int ammoHeight = EQUIP_MENU_SLOT_HEIGHT * 2 + FontH();
const int ammoY = CENTER_Y(pos, size, ammoHeight) - 12;
const color_t color = d->Active ? colorWhite : colorGray;
const struct vec2i bgSize =
svec2i(CLAMP(d->size.x * 3 / 4, 40 * 2, 40 * 4), FontH() * 3 + 4);
const struct vec2i scrollSize = svec2i(bgSize.x, SCROLL_H);
bool scrollDown = false;
CA_FOREACH(const int, ammoId, d->ammoIds)
const Ammo *ammo = AmmoGetById(&gAmmo, *ammoId);
if (_ca_index >= d->scroll && _ca_index < d->scroll + AMMO_MENU_MAX_ROWS)
for (Option option = OPTION_HP; option < OPTION_COUNT; option++)
{
DrawAmmoMenuItem(
d, g, _ca_index, ammo, svec2i(pos.x, pos.y + ammoY), bgSize);
DrawUtilMenuItem(d, g, option, svec2i(pos.x, pos.y + ammoY), bgSize);
}
if (_ca_index == d->scroll + AMMO_MENU_MAX_ROWS)
{
scrollDown = true;
break;
}
CA_FOREACH_END()
// Draw scroll buttons
const Pic *gradient = PicManagerGetPic(&gPicManager, "hud/gradient");
if (d->scroll > 0)
{
// TODO: test scrolling
const Pic *scrollPic = d->buttonBG;
const Rect2i scrollRect =
Rect2iNew(svec2i(pos.x + 3, pos.y + 1 + ammoY), scrollSize);
PicRender(
gradient, g->gameWindow.renderer,
svec2i(
scrollRect.Pos.x + scrollSize.x / 2,
pos.y + gradient->size.y / 2 + ammoY + scrollSize.y - 1),
colorBlack, 0, svec2((float)scrollSize.x, 1), SDL_FLIP_NONE,
Rect2iZero());
Draw9Slice(
g, scrollPic, scrollRect, 3, 3, 3, 3, true, color, SDL_FLIP_NONE);
FontOpts fopts = FontOptsNew();
fopts.Area = scrollRect.Size;
fopts.HAlign = ALIGN_CENTER;
fopts.VAlign = ALIGN_CENTER;
fopts.Mask = color;
FontStrOpt(ARROW_UP, scrollRect.Pos, fopts);
}
if (scrollDown)
{
const Pic *scrollPic = d->buttonBG;
const Rect2i scrollRect = Rect2iNew(
svec2i(
pos.x + 3,
pos.y - 1 + ammoY + bgSize.y * AMMO_MENU_MAX_ROWS - SCROLL_H),
scrollSize);
PicRender(
gradient, g->gameWindow.renderer,
svec2i(
scrollRect.Pos.x + scrollSize.x / 2,
pos.y - gradient->size.y / 2 + ammoY +
bgSize.y * AMMO_MENU_MAX_ROWS - SCROLL_H -
gradient->size.y / 2 + 1),
colorBlack, 0, svec2((float)scrollSize.x, 1), SDL_FLIP_VERTICAL,
Rect2iZero());
Draw9Slice(
g, scrollPic, scrollRect, 3, 3, 3, 3, true, color, SDL_FLIP_NONE);
FontOpts fopts = FontOptsNew();
fopts.Area = scrollRect.Size;
fopts.HAlign = ALIGN_CENTER;
fopts.VAlign = ALIGN_CENTER;
fopts.Mask = color;
FontStrOpt(ARROW_DOWN, scrollRect.Pos, fopts);
}
else
{
// Draw back item
DrawAmmoMenuItem(
d, g, (int)d->ammoIds.size, NULL, svec2i(pos.x, pos.y + ammoY),
bgSize);
}
// Draw back item
DrawUtilMenuItem(d, g, OPTION_COUNT, svec2i(pos.x, pos.y + ammoY), bgSize);
}
static void DrawAmmoMenuItem(
const AmmoMenu *data, GraphicsDevice *g, const int idx, const Ammo *a,
static void DrawUtilMenuItem(
const UtilMenu *data, GraphicsDevice *g, const int idx,
const struct vec2i pos, const struct vec2i bgSize)
{
const bool selected = data->idx / 2 == idx;
const PlayerData *pData = PlayerDataGetByUID(data->PlayerUID);
const int ammoId = idx < (int)data->ammoIds.size
? *(int *)CArrayGet(&data->ammoIds, idx)
: -1;
const int ammoAmount = PlayerGetAmmoAmount(pData, ammoId);
const struct vec2i bgPos =
svec2i(pos.x, pos.y + (idx - data->scroll) * bgSize.y);
// Disallow buy/sell if ammo is free
const bool enabled = a == NULL || (data->Active && a->Price > 0 &&
a->Price <= pData->Totals.Score);
color_t color = enabled ? colorWhite : colorGray;
const Option option = GetSelectedOption(data);
int amount = 0;
int max = 0;
int price = 0;
const char *name = NULL;
const Pic *pic = NULL;
switch (option)
{
case OPTION_HP:
amount = pData->hp;
max = CampaignGetMaxHP(&gCampaign);
price = HP_PRICE;
name = "Armor";
pic = PicManagerGetPic(&gPicManager, "health");
break;
case OPTION_LIVES:
amount = pData->Lives;
max = CampaignGetMaxLives(&gCampaign);
price = LIFE_PRICE;
name = "Life";
pic = GetHeadPic(
pData->Char.Class, DIRECTION_DOWN, false, &pData->Char.Colors);
break;
default:
name = "Back";
break;
}
const struct vec2i bgPos = svec2i(pos.x, pos.y + idx * bgSize.y);
color_t color = colorWhite;
if (selected && data->Active)
{
const color_t cbg = {0, 255, 255, 64};
@@ -297,52 +258,49 @@ static void DrawAmmoMenuItem(
// With: amount/max coloured rectangle
int x = bgPos.x + 4;
int y = bgPos.y + AMMO_ROW_H * 2;
int y = bgPos.y + ROW_H * 2;
// Ammo amount BG
if (a && a->Max > 0 && ammoAmount > 0)
// Amount BG
if (max > 0 && amount > 0)
{
const color_t gaugeBG =
AmmoIsLow(a, ammoAmount) ? colorRed : colorBlue;
DrawRectangle(
g, svec2i_add(svec2i(x, y), svec2i_one()),
svec2i(ammoAmount * (bgSize.x - 8) / a->Max, FontH()), gaugeBG,
true);
svec2i(amount * (bgSize.x - 8) / max, FontH()), colorBlue, true);
}
y = bgPos.y + AMMO_ROW_H;
y = bgPos.y + ROW_H;
// Sell/buy buttons
if (a != NULL)
if (max > 0)
{
const bool sellSelected =
selected && data->Active && (data->idx & 1) == 1;
const bool canSell = CanSell(data, ammoId);
const bool canSell = amount > max;
const FontOpts foptsSell = {
ALIGN_CENTER, ALIGN_START, svec2i(AMMO_BUTTON_BG_W, FontH()),
ALIGN_CENTER, ALIGN_START, svec2i(BUTTON_BG_W, FontH()),
svec2i(2, 2), sellSelected ? colorRed : colorGray};
x = bgPos.x + bgSize.x - AMMO_BUTTON_BG_W - 2;
x = bgPos.x + bgSize.x - BUTTON_BG_W - 2;
const struct vec2i sellPos = svec2i(x, y);
Draw9Slice(
g, data->buttonBG,
Rect2iNew(sellPos, svec2i(AMMO_BUTTON_BG_W, FontH() + 4)), 3, 3, 3,
3, true,
Rect2iNew(sellPos, svec2i(BUTTON_BG_W, FontH() + 4)), 3, 3, 3, 3,
true,
canSell ? (sellSelected ? colorRed : colorMaroon) : colorGray,
SDL_FLIP_NONE);
FontStrOpt("Sell", sellPos, foptsSell);
x -= AMMO_BUTTON_BG_W + 3;
x -= BUTTON_BG_W + 3;
const bool buySelected =
selected && data->Active && (data->idx & 1) == 0;
const bool canBuy = CanBuy(data, ammoId);
const bool canBuy = amount < max && price <= pData->Totals.Score;
const FontOpts foptsBuy = {
ALIGN_CENTER, ALIGN_START, svec2i(AMMO_BUTTON_BG_W, FontH()),
ALIGN_CENTER, ALIGN_START, svec2i(BUTTON_BG_W, FontH()),
svec2i(2, 2), buySelected ? colorGreen : colorGray};
const struct vec2i buyPos = svec2i(x, y);
Draw9Slice(
g, data->buttonBG,
Rect2iNew(buyPos, svec2i(AMMO_BUTTON_BG_W, FontH() + 4)), 3, 3, 3,
3, true,
Rect2iNew(buyPos, svec2i(BUTTON_BG_W, FontH() + 4)), 3, 3, 3, 3,
true,
canBuy ? (buySelected ? colorGreen : colorOfficeGreen) : colorGray,
SDL_FLIP_NONE);
FontStrOpt("Buy", buyPos, foptsBuy);
@@ -355,68 +313,57 @@ static void DrawAmmoMenuItem(
ALIGN_START, ALIGN_START, bgSize, svec2i(2, 2), color};
// Name
FontStrOpt(a ? a->Name : "Back", svec2i(x, y), fopts);
FontStrOpt(name, svec2i(x, y), fopts);
// Price
if (a && a->Price > 0)
if (price > 0)
{
const FontOpts foptsP = {
ALIGN_END, ALIGN_START, bgSize, svec2i(8, 2),
enabled ? (selected ? colorRed : colorGray) : colorDarkGray};
selected ? colorRed : colorGray};
char buf[256];
sprintf(buf, "$%d", a->Price);
sprintf(buf, "$%d", price);
FontStrOpt(buf, svec2i(x, y), foptsP);
}
y += AMMO_ROW_H * 2;
y += ROW_H * 2;
x = bgPos.x + 4;
// Ammo amount BG
if (a && a->Max > 0 && ammoAmount > 0)
// Amount BG
if (max > 0 && amount > 0)
{
const color_t gaugeBG =
AmmoIsLow(a, ammoAmount) ? colorRed : colorBlue;
DrawRectangle(
g, svec2i_add(svec2i(x, y), svec2i_one()),
svec2i(ammoAmount * (bgSize.x - 8) / a->Max, FontH()), gaugeBG,
true);
svec2i(amount * (bgSize.x - 8) / max, FontH()), colorBlue, true);
}
// Amount
if (a != NULL)
if (max > 0)
{
char buf[256];
if (a->Max > 0)
{
sprintf(buf, "%d/%d", ammoAmount, a->Max);
}
else
{
sprintf(buf, "%d", ammoAmount);
}
sprintf(buf, "%d/%d", amount, max);
const FontOpts foptsA = {
ALIGN_END, ALIGN_START, bgSize, svec2i(8, 2), color};
FontStrOpt(buf, svec2i(x, y), foptsA);
}
y -= AMMO_ROW_H;
y -= ROW_H;
// Icon
if (a != NULL)
if (pic != NULL)
{
x = bgPos.x + 12;
CPicDrawContext c = CPicDrawContextNew();
const struct vec2i ammoPos = svec2i_subtract(
const struct vec2i picPos = svec2i_subtract(
svec2i(x, bgPos.y + bgSize.y / 2),
svec2i_scale_divide(CPicGetPic(&a->Pic, 0)->size, 2));
CPicDraw(g, &a->Pic, ammoPos, &c);
svec2i_scale_divide(pic->size, 2));
PicRender(
pic, g->gameWindow.renderer, picPos, colorWhite, 0, svec2_one(),
SDL_FLIP_NONE, Rect2iZero());
}
}
static int HandleInputMenu(int cmd, void *data)
{
AmmoMenu *d = data;
const int numAmmo = (int)d->ammoIds.size;
UtilMenu *d = data;
if (cmd & CMD_BUTTON1)
{
@@ -436,7 +383,7 @@ static int HandleInputMenu(int cmd, void *data)
}
else if (cmd & CMD_RIGHT)
{
if (d->idx < numAmmo * 2 && (d->idx % 2) == 0)
if (d->idx < OPTION_COUNT * 2 && (d->idx % 2) == 0)
{
d->idx++;
MenuPlaySound(MENU_SOUND_SWITCH);
@@ -452,15 +399,13 @@ static int HandleInputMenu(int cmd, void *data)
}
else if (cmd & CMD_DOWN)
{
if (d->idx + 2 < numAmmo * 2 + 2)
if (d->idx + 2 < OPTION_COUNT * 2 + 2)
{
d->idx = MIN(numAmmo * 2 + 2, d->idx + 2);
d->idx = MIN(OPTION_COUNT * 2 + 2, d->idx + 2);
MenuPlaySound(MENU_SOUND_SWITCH);
}
}
d->scroll = ClampScroll(d);
return 0;
}
@@ -471,8 +416,7 @@ void UtilMenuTerminate(UtilMenu *menu)
void UtilMenuReset(UtilMenu *menu)
{
const PlayerData *pData = PlayerDataGetByUID(menu->PlayerUID);
menu->idx = CLAMP(menu->idx, 0, (int)menu->ammoIds.size);
menu->idx = CLAMP(menu->idx, 0, OPTION_COUNT * 2);
}
void UtilMenuActivate(UtilMenu *menu)
@@ -490,10 +434,17 @@ bool UtilMenuUpdate(UtilMenu *menu, const int cmd)
case UTIL_MENU_NONE:
break;
case UTIL_MENU_SELECT: {
const int ammoId = GetSelectedAmmo(menu);
const Option option = GetSelectedOption(menu);
const bool buy = (menu->idx & 1) == 0;
const int amount = AmmoGetById(&gAmmo, ammoId)->Amount;
PlayerAddAmmo(p, ammoId, buy ? amount : -amount, false);
switch (option)
{
case OPTION_HP:
PlayerSetHP(p, p->hp + (buy ? HP_DELTA : -HP_DELTA));
break;
case OPTION_LIVES:
PlayerSetLives(p, p->Lives + (buy ? 1 : -1));
break;
}
return true;
}
break;

View File

@@ -46,7 +46,6 @@ typedef struct
const Pic *buttonBG;
int idx;
struct vec2i size;
int scroll;
} UtilMenu;
void UtilMenuCreate(