mirror of
https://github.com/cxong/cdogs-sdl.git
synced 2025-07-23 07:23:01 +02:00
WIP buy/sell HP/lives #757
This commit is contained in:
@@ -8,5 +8,8 @@
|
||||
"BuyAndSell": true,
|
||||
"RandomPickups": false,
|
||||
"DoorOpenTicks": 0,
|
||||
"MaxLives": 4,
|
||||
"PlayerHP": 20,
|
||||
"PlayerMaxHP": 50,
|
||||
"Missions": 1
|
||||
}
|
@@ -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]]
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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(
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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(
|
||||
|
17
src/prep.c
17
src/prep.c
@@ -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");
|
||||
|
383
src/util_menu.c
383
src/util_menu.c
@@ -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;
|
||||
|
@@ -46,7 +46,6 @@ typedef struct
|
||||
const Pic *buttonBG;
|
||||
int idx;
|
||||
struct vec2i size;
|
||||
int scroll;
|
||||
} UtilMenu;
|
||||
|
||||
void UtilMenuCreate(
|
||||
|
Reference in New Issue
Block a user