Quizzes, pickup menus #712

This commit is contained in:
Cong
2024-10-07 23:05:16 +11:00
parent 1edbb0b0b8
commit f0c8932d4c
17 changed files with 708 additions and 471 deletions

View File

@@ -114,6 +114,9 @@
"Type": "Ammo",
"Ammo": "Feed",
"Amount": 10
}, {
"Type": "Sound",
"Sound": "bonus"
}]
}, {
"Text": "11",
@@ -121,8 +124,8 @@
"Type": "Health",
"Health": 4
}, {
"Type": "Health",
"Health": 4
"Type": "Sound",
"Sound": "wrong"
}]
}]
}

View File

@@ -4,6 +4,7 @@ include_directories(
add_definitions(-DSTATIC)
set(CDOGS_SOURCES
actor_fire.c
actor_pickup.c
actor_placement.c
actors.c
aheasing/easing.c
@@ -115,6 +116,7 @@ set(CDOGS_SOURCES
yajl_utils.c)
set(CDOGS_HEADERS
actor_fire.h
actor_pickup.h
actor_placement.h
actors.h
aheasing/easing.h

313
src/cdogs/actor_pickup.c Normal file
View File

@@ -0,0 +1,313 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2015, 2017-2020, 2022-2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "actor_pickup.h"
#include "campaigns.h"
#include "gamedata.h"
#include "net_util.h"
static bool TreatAsGunPickup(const PickupEffect *pe, const TActor *a);
static bool TryPickupAmmo(TActor *a, const Pickup *p, const PickupEffect *pe);
static bool TryPickupGun(
TActor *a, const PickupEffect *pe, const bool pickupAll,
const char **sound);
void PickupPickup(TActor *a, Pickup *p, const bool pickupAll)
{
if (p->PickedUp)
return;
CASSERT(a->PlayerUID >= 0, "NPCs cannot pickup");
// can always pickup effect-less pickups
bool canPickup = p->class->Effects.size == 0;
const char *sound = p->class->Sound;
GameEvent e;
CA_FOREACH(const PickupEffect, pe, p->class->Effects)
canPickup = PickupApplyEffect(a, p, pe, pickupAll, &sound);
CA_FOREACH_END()
if (canPickup)
{
if (sound != NULL)
{
e = GameEventNew(GAME_EVENT_SOUND_AT);
strcpy(e.u.SoundAt.Sound, sound);
e.u.SoundAt.Pos = Vec2ToNet(a->thing.Pos);
GameEventsEnqueue(&gGameEvents, e);
}
e = GameEventNew(GAME_EVENT_REMOVE_PICKUP);
e.u.RemovePickup.UID = p->UID;
e.u.RemovePickup.SpawnerUID = p->SpawnerUID;
GameEventsEnqueue(&gGameEvents, e);
// Prevent multiple pickups by marking
p->PickedUp = true;
a->PickupAll = false;
}
}
bool PickupApplyEffect(
TActor *a, const Pickup *p, const PickupEffect *pe, const bool force,
char **sound)
{
bool canPickup = false;
GameEvent e;
switch (pe->Type)
{
case PICKUP_JEWEL: {
canPickup = true;
e = GameEventNew(GAME_EVENT_SCORE);
e.u.Score.PlayerUID = a->PlayerUID;
e.u.Score.Score = pe->u.Score;
GameEventsEnqueue(&gGameEvents, e);
e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
e.u.AddParticle.Class =
StrParticleClass(&gParticleClasses, "score_text");
e.u.AddParticle.ActorUID = a->uid;
e.u.AddParticle.Pos = p->thing.Pos;
e.u.AddParticle.DZ = 3;
if (gCampaign.Setting.Ammo)
{
sprintf(e.u.AddParticle.Text, "$%d", pe->u.Score);
}
else
{
sprintf(e.u.AddParticle.Text, "+%d", pe->u.Score);
}
GameEventsEnqueue(&gGameEvents, e);
UpdateMissionObjective(
&gMission, p->thing.flags, OBJECTIVE_COLLECT, 1);
}
break;
case PICKUP_HEALTH:
// Don't pick up unless taken damage
if (a->health < ActorGetMaxHeal(a, pe->u.Heal.ExceedMax))
{
canPickup = true;
e = GameEventNew(GAME_EVENT_ACTOR_HEAL);
e.u.Heal.UID = a->uid;
e.u.Heal.PlayerUID = a->PlayerUID;
e.u.Heal.Amount = pe->u.Heal.Amount;
e.u.Heal.ExceedMax = pe->u.Heal.ExceedMax;
e.u.Heal.IsRandomSpawned = p->IsRandomSpawned;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_AMMO: // fallthrough
case PICKUP_GUN:
if (TreatAsGunPickup(pe, a))
{
canPickup = TryPickupGun(a, pe, force, sound) || canPickup;
}
else
{
canPickup = TryPickupAmmo(a, p, pe) || canPickup;
}
break;
case PICKUP_KEYCARD: {
canPickup = true;
e = GameEventNew(GAME_EVENT_ADD_KEYS);
e.u.AddKeys.KeyFlags = pe->u.Keys;
e.u.AddKeys.Pos = Vec2ToNet(a->thing.Pos);
GameEventsEnqueue(&gGameEvents, e);
if (*sound == NULL)
{
*sound = "key";
}
}
break;
case PICKUP_SHOW_MAP: {
canPickup = true;
e = GameEventNew(GAME_EVENT_EXPLORE_TILES);
e.u.ExploreTiles.Runs_count = 1;
e.u.ExploreTiles.Runs[0].Run = gMap.Size.x * gMap.Size.y;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_LIVES: {
canPickup = true;
e = GameEventNew(GAME_EVENT_PLAYER_ADD_LIVES);
e.u.PlayerAddLives.UID = a->PlayerUID;
e.u.PlayerAddLives.Lives = pe->u.Lives;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_SOUND:
canPickup = true;
*sound = pe->u.Sound;
break;
case PICKUP_MENU:
canPickup = force;
if (force)
{
a->pickupMenu.pickup = p;
a->pickupMenu.effect = pe;
a->pickupMenu.index = 0;
}
break;
default:
CASSERT(false, "unexpected pickup type");
break;
}
return canPickup;
}
static bool HasGunUsingAmmo(const TActor *a, const int ammoId);
static bool TreatAsGunPickup(const PickupEffect *pe, const TActor *a)
{
// Grenades can also be gun pickups; treat as gun pickup if the player
// doesn't have its ammo
switch (pe->Type)
{
case PICKUP_AMMO:
if (!HasGunUsingAmmo(a, pe->u.Ammo.Id))
{
const Ammo *ammo = AmmoGetById(&gAmmo, pe->u.Ammo.Id);
if (ammo->DefaultGun)
{
return true;
}
}
return false;
case PICKUP_GUN: {
const WeaponClass *wc = IdWeaponClass(pe->u.GunId);
return wc->Type != GUNTYPE_GRENADE ||
!HasGunUsingAmmo(a, wc->u.Normal.AmmoId);
}
default:
CASSERT(false, "unexpected pickup type");
return false;
}
}
static bool HasGunUsingAmmo(const TActor *a, const int ammoId)
{
for (int i = 0; i < MAX_WEAPONS; i++)
{
const WeaponClass *wc = a->guns[i].Gun;
if (wc == NULL)
continue;
for (int j = 0; j < WeaponClassNumBarrels(wc); j++)
{
if (WC_BARREL_ATTR(*wc, AmmoId, j) == ammoId)
{
return true;
}
}
}
return false;
}
static bool TryPickupAmmo(TActor *a, const Pickup *p, const PickupEffect *pe)
{
// Don't pickup if not using ammo
if (!gCampaign.Setting.Ammo)
{
return false;
}
// Don't pickup if ammo full
const int ammoId = pe->Type == PICKUP_AMMO
? (int)pe->u.Ammo.Id
: IdWeaponClass(pe->u.GunId)->u.Normal.AmmoId;
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int amount =
pe->Type == PICKUP_AMMO ? (int)pe->u.Ammo.Amount : ammo->Amount;
const int current = *(int *)CArrayGet(&a->ammo, ammoId);
if (ammo->Max > 0 && current >= ammo->Max)
{
return false;
}
// Take ammo
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD_AMMO);
e.u.AddAmmo.UID = a->uid;
e.u.AddAmmo.PlayerUID = a->PlayerUID;
e.u.AddAmmo.Ammo.Id = ammoId;
e.u.AddAmmo.Ammo.Amount = amount;
e.u.AddAmmo.IsRandomSpawned = p->IsRandomSpawned;
// Note: receiving end will prevent ammo from exceeding max
GameEventsEnqueue(&gGameEvents, e);
return true;
}
static bool TryPickupGun(
TActor *a, const PickupEffect *pe, const bool pickupAll,
const char **sound)
{
// Guns can only be picked up manually
if (!pickupAll)
{
return false;
}
// When picking up a gun, the actor always ends up with it equipped, but:
// - If the player already has the gun:
// - Switch to the same gun and drop the same gun
// - If the player doesn't have the gun:
// - If the player has an empty slot, pickup the gun into that slot
// - If the player doesn't have an empty slot, replace the current gun,
// dropping it in the process
const WeaponClass *wc =
pe->Type == PICKUP_GUN
? IdWeaponClass(pe->u.GunId)
: StrWeaponClass(AmmoGetById(&gAmmo, pe->u.Ammo.Id)->DefaultGun);
ActorPickupGun(a, wc);
// If the player has less ammo than the default amount,
// replenish up to this amount
// TODO: support multi gun
const int ammoId = WC_BARREL_ATTR(*wc, AmmoId, 0);
if (ammoId >= 0)
{
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int ammoDeficit = ammo->Amount * AMMO_STARTING_MULTIPLE -
*(int *)CArrayGet(&a->ammo, ammoId);
if (ammoDeficit > 0)
{
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD_AMMO);
e.u.AddAmmo.UID = a->uid;
e.u.AddAmmo.PlayerUID = a->PlayerUID;
e.u.AddAmmo.Ammo.Id = ammoId;
e.u.AddAmmo.Ammo.Amount = ammoDeficit;
e.u.AddAmmo.IsRandomSpawned = false;
GameEventsEnqueue(&gGameEvents, e);
// Also play an ammo pickup sound
*sound = ammo->Sound;
}
}
return true;
}

36
src/cdogs/actor_pickup.h Normal file
View File

@@ -0,0 +1,36 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2015, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "actors.h"
#include "pickup.h"
void PickupPickup(TActor *a, Pickup *p, const bool pickupAll);
bool PickupApplyEffect(
TActor *a, const Pickup *p, const PickupEffect *pe, const bool force,
char **sound);

View File

@@ -55,6 +55,7 @@
#include <string.h>
#include "actor_fire.h"
#include "actor_pickup.h"
#include "actor_placement.h"
#include "ai.h"
#include "ai_coop.h"
@@ -74,7 +75,6 @@
#include "material.h"
#include "mission.h"
#include "pic_manager.h"
#include "pickup.h"
#include "sounds.h"
#include "thing.h"
#include "triggers.h"
@@ -1057,71 +1057,126 @@ static bool TryGrenade(TActor *a, const int cmd)
static bool ActorTryMove(TActor *actor, int cmd, int ticks);
void CommandActor(TActor *actor, int cmd, int ticks)
{
actor->hasShot = false;
// If this is a pilot, command the vehicle instead
if (actor->vehicleUID != -1)
GameEvent e;
// If the actor is currently using a menu, control the menu instead
if (actor->pickupMenu.pickup)
{
TActor *vehicle = ActorGetByUID(actor->vehicleUID);
CommandActor(vehicle, cmd, ticks);
}
else if (actor->pilotUID == -1)
{
// If this is a vehicle and there's no pilot, do nothing
if (Button1(cmd) && !Button1(actor->lastCmd))
{
// Apply effects of pickup menu item and reset
const PickupMenuItem *m = CArrayGet(
&actor->pickupMenu.effect->u.Menu.Items,
actor->pickupMenu.index);
bool canPickup = false;
const char *sound = "menu_enter";
CA_FOREACH(const PickupEffect, pe, m->Effects)
CASSERT(pe->Type != PICKUP_MENU, "can't have nested menu effects");
canPickup = PickupApplyEffect(
actor, actor->pickupMenu.pickup, pe, true, &sound);
CA_FOREACH_END()
if (canPickup && sound != NULL)
{
e = GameEventNew(GAME_EVENT_SOUND_AT);
strcpy(e.u.SoundAt.Sound, sound);
e.u.SoundAt.Pos = Vec2ToNet(actor->thing.Pos);
GameEventsEnqueue(&gGameEvents, e);
}
actor->pickupMenu.pickup = NULL;
actor->pickupMenu.effect = NULL;
actor->pickupMenu.index = 0;
KeyLockKeys(&gEventHandlers.keyboard);
JoyLock(&gEventHandlers.joysticks);
}
else if (Up(cmd) && !Up(actor->lastCmd))
{
actor->pickupMenu.index--;
if (actor->pickupMenu.index < 0)
{
actor->pickupMenu.index =
(int)actor->pickupMenu.effect->u.Menu.Items.size - 1;
}
SoundPlay(&gSoundDevice, StrSound("menu_switch"));
}
else if (Down(cmd) && !Down(actor->lastCmd))
{
actor->pickupMenu.index++;
if (actor->pickupMenu.index ==
(int)actor->pickupMenu.effect->u.Menu.Items.size)
{
actor->pickupMenu.index = 0;
}
SoundPlay(&gSoundDevice, StrSound("menu_switch"));
}
}
else
{
if (actor->confused)
actor->hasShot = false;
// If this is a pilot, command the vehicle instead
if (actor->vehicleUID != -1)
{
cmd = CmdGetReverse(cmd);
TActor *vehicle = ActorGetByUID(actor->vehicleUID);
CommandActor(vehicle, cmd, ticks);
}
else if (actor->pilotUID == -1)
{
// If this is a vehicle and there's no pilot, do nothing
}
else
{
if (actor->confused)
{
cmd = CmdGetReverse(cmd);
}
if (actor->health > 0)
{
const bool hasChangedDirection =
ActorTryChangeDirection(actor, cmd, actor->lastCmd);
actor->hasShot = ActorTryShoot(actor, cmd);
const bool hasGrenaded = TryGrenade(actor, cmd);
const bool hasMoved = ActorTryMove(actor, cmd, ticks);
ActorAnimation anim = actor->anim.Type;
// Idle if player hasn't done anything
if (!(hasChangedDirection || actor->hasShot || hasGrenaded ||
hasMoved))
{
anim = ACTORANIMATION_IDLE;
}
else if (hasMoved)
{
anim = ACTORANIMATION_WALKING;
}
else
{
anim = ACTORANIMATION_STAND;
}
if (actor->anim.Type != anim)
{
e = GameEventNew(GAME_EVENT_ACTOR_STATE);
e.u.ActorState.UID = actor->uid;
e.u.ActorState.State = (int32_t)anim;
GameEventsEnqueue(&gGameEvents, e);
}
}
}
if (actor->health > 0)
actor->specialCmdDir = CMD_HAS_DIRECTION(cmd);
if (Button2(cmd) && !actor->specialCmdDir)
{
const bool hasChangedDirection =
ActorTryChangeDirection(actor, cmd, actor->lastCmd);
actor->hasShot = ActorTryShoot(actor, cmd);
const bool hasGrenaded = TryGrenade(actor, cmd);
const bool hasMoved = ActorTryMove(actor, cmd, ticks);
ActorAnimation anim = actor->anim.Type;
// Idle if player hasn't done anything
if (!(hasChangedDirection || actor->hasShot || hasGrenaded ||
hasMoved))
// Special: pick up things that can only be picked up on demand
if (!actor->PickupAll && !Button2(actor->lastCmd) &&
actor->vehicleUID == -1)
{
anim = ACTORANIMATION_IDLE;
}
else if (hasMoved)
{
anim = ACTORANIMATION_WALKING;
}
else
{
anim = ACTORANIMATION_STAND;
}
if (actor->anim.Type != anim)
{
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_STATE);
e.u.ActorState.UID = actor->uid;
e.u.ActorState.State = (int32_t)anim;
e = GameEventNew(GAME_EVENT_ACTOR_PICKUP_ALL);
e.u.ActorPickupAll.UID = actor->uid;
e.u.ActorPickupAll.PickupAll = true;
GameEventsEnqueue(&gGameEvents, e);
}
}
}
actor->specialCmdDir = CMD_HAS_DIRECTION(cmd);
if (Button2(cmd) && !actor->specialCmdDir)
{
// Special: pick up things that can only be picked up on demand
if (!actor->PickupAll && !Button2(actor->lastCmd) &&
actor->vehicleUID == -1)
{
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_PICKUP_ALL);
e.u.ActorPickupAll.UID = actor->uid;
e.u.ActorPickupAll.PickupAll = true;
GameEventsEnqueue(&gGameEvents, e);
}
}
actor->lastCmd = cmd;
}
static bool ActorTryMove(TActor *actor, int cmd, int ticks)
@@ -1384,26 +1439,7 @@ static bool CheckManualPickupFunc(
strcpy(buttonName, "");
InputGetButtonName(
pData->inputDevice, pData->deviceIndex, CMD_BUTTON2, buttonName);
// TODO: PickupGetName
const char *pickupName = NULL;
CA_FOREACH(const PickupEffect, pe, p->class->Effects)
switch (pe->Type)
{
case PICKUP_AMMO:
pickupName = AmmoGetById(&gAmmo, pe->u.Ammo.Id)->Name;
break;
case PICKUP_GUN:
pickupName = IdWeaponClass(pe->u.GunId)->name;
break;
default:
break;
}
if (pickupName != NULL)
{
break;
}
CA_FOREACH_END()
CASSERT(pickupName != NULL, "unknown pickup name");
const char *pickupName = PickupClassGetName(p->class);
char buf[256];
sprintf(buf, "%s to pick up\n%s", buttonName, pickupName);
ActorSetChatter(a, buf, 2);

View File

@@ -54,6 +54,7 @@
#include "game_mode.h"
#include "grafx.h"
#include "mathc/mathc.h"
#include "pickup.h"
#include "player.h"
#include "thing.h"
#include "weapon.h"
@@ -87,8 +88,8 @@
#define FLAGS_SNEAKY (1 << 23) // Always shoot back when player shoots
#define FLAGS_SLEEPALWAYS (1 << 24)
#define FLAGS_AWAKEALWAYS (1 << 25)
#define FLAGS_RESCUED (1 << 26) // Run towards exit
#define FLAGS_MOVE_AND_SHOOT (1 << 27) // Can move and shoot at the same time
#define FLAGS_RESCUED (1 << 26) // Run towards exit
#define FLAGS_MOVE_AND_SHOOT (1 << 27) // Can move and shoot at the same time
typedef enum
{
@@ -126,10 +127,10 @@ typedef struct Actor
// -1 if human character (get from player data), otherwise index into
// CharacterStore OtherChars
int charId;
int PlayerUID; // -1 unless a human player
int uid; // unique ID across all actors
int pilotUID; // the actor that controls this
// (same as uid for normal actors)
int PlayerUID; // -1 unless a human player
int uid; // unique ID across all actors
int pilotUID; // the actor that controls this
// (same as uid for normal actors)
int vehicleUID; // -1 unless piloting a vehicle
Weapon guns[MAX_WEAPONS];
CArray ammo; // of int
@@ -174,6 +175,14 @@ typedef struct Actor
bool hasShot;
// Whether actor is in a pickup menu and their current selection
struct
{
const Pickup *pickup;
const PickupEffect *effect;
int index;
} pickupMenu;
// Signals to other AIs what this actor is doing
ActorAction action;
AIContext *aiContext;

View File

@@ -1,30 +1,30 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2018 Cong Xu
All rights reserved.
Copyright (c) 2013-2018, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "color.h"
@@ -34,56 +34,57 @@
#include "utils.h"
color_t colorWhite = { 255, 255, 255, 255 };
color_t colorRed = { 255, 0, 0, 255 };
color_t colorGreen = { 0, 255, 0, 255 };
color_t colorBlue = { 0, 0, 255, 255 };
color_t colorPoison = { 64, 192, 64, 255 };
color_t colorBlack = { 0, 0, 0, 255 };
color_t colorDarker = { 192, 192, 192, 255 };
color_t colorPurple = { 192, 0, 192, 255 };
color_t colorGray = { 128, 128, 128, 255 };
color_t colorDarkGray = { 64, 64, 64, 255 };
color_t colorYellow = { 255, 255, 128, 255 };
color_t colorMagenta = { 255, 0, 255, 255 };
color_t colorCyan = { 0, 255, 255, 255 };
color_t colorFog = { 96, 96, 96, 255 };
color_t colorOrange = { 255, 128, 0, 255 };
color_t colorTransparent = { 0, 0, 0, 0 };
color_t colorWhite = {255, 255, 255, 255};
color_t colorRed = {255, 0, 0, 255};
color_t colorGreen = {0, 255, 0, 255};
color_t colorBlue = {0, 0, 255, 255};
color_t colorPoison = {64, 192, 64, 255};
color_t colorBlack = {0, 0, 0, 255};
color_t colorDarker = {192, 192, 192, 255};
color_t colorPurple = {192, 0, 192, 255};
color_t colorGray = {128, 128, 128, 255};
color_t colorDarkGray = {64, 64, 64, 255};
color_t colorYellow = {255, 255, 128, 255};
color_t colorMagenta = {255, 0, 255, 255};
color_t colorCyan = {0, 255, 255, 255};
color_t colorFog = {96, 96, 96, 255};
color_t colorOrange = {255, 128, 0, 255};
color_t colorTransparent = {0, 0, 0, 0};
color_t colorMaroon = { 0x84, 0, 0, 255 };
color_t colorLonestar = { 0x70, 0, 0, 255 };
color_t colorRusticRed = { 0x48, 0, 0, 255 };
color_t colorOfficeGreen = { 0, 0x84, 0, 255 };
color_t colorPakistanGreen = { 0, 0x70, 0, 255 };
color_t colorDarkFern = { 0, 0x48, 0, 255 };
color_t colorNavyBlue = { 0, 0, 0x84, 255 };
color_t colorArapawa = { 0, 0, 0x70, 255 };
color_t colorStratos = { 0, 0, 0x48, 255 };
color_t colorPatriarch = { 0x84, 0, 0x84, 255 };
color_t colorPompadour = { 0x70, 0, 0x70, 255 };
color_t colorLoulou = { 0x48, 0, 0x48, 255 };
color_t colorBattleshipGrey = { 0x84, 0x84, 0x84, 255 };
color_t colorDoveGray = { 0x70, 0x70, 0x70, 255 };
color_t colorGravel = { 0x48, 0x48, 0x48, 255 };
color_t colorComet = { 0x5C, 0x5C, 0x84, 255 };
color_t colorFiord = { 0x48, 0x48, 0x70, 255 };
color_t colorTuna = { 0x34, 0x34, 0x48, 255 };
color_t colorHacienda = { 0x94, 0x80, 0x2C, 255 };
color_t colorKumera = { 0x84, 0x70, 0x24, 255 };
color_t colorHimalaya = { 0x74, 0x60, 0x1C, 255 };
color_t colorChocolate = { 0x84, 0x44, 0, 255 };
color_t colorNutmeg = { 0x70, 0x38, 0, 255 };
color_t colorBracken = { 0x48, 0x24, 0, 255 };
color_t colorTeal = { 0, 0x84, 0x84, 255 };
color_t colorSkobeloff = { 0, 0x70, 0x70, 255 };
color_t colorDeepJungleGreen = { 0, 0x48, 0x48, 255 };
color_t colorMaroon = {0x84, 0, 0, 255};
color_t colorLonestar = {0x70, 0, 0, 255};
color_t colorRusticRed = {0x48, 0, 0, 255};
color_t colorOfficeGreen = {0, 0x84, 0, 255};
color_t colorPakistanGreen = {0, 0x70, 0, 255};
color_t colorDarkFern = {0, 0x48, 0, 255};
color_t colorNavyBlue = {0, 0, 0x84, 255};
color_t colorArapawa = {0, 0, 0x70, 255};
color_t colorStratos = {0, 0, 0x48, 255};
color_t colorPatriarch = {0x84, 0, 0x84, 255};
color_t colorPompadour = {0x70, 0, 0x70, 255};
color_t colorLoulou = {0x48, 0, 0x48, 255};
color_t colorBattleshipGrey = {0x84, 0x84, 0x84, 255};
color_t colorDoveGray = {0x70, 0x70, 0x70, 255};
color_t colorGravel = {0x48, 0x48, 0x48, 255};
color_t colorComet = {0x5C, 0x5C, 0x84, 255};
color_t colorFiord = {0x48, 0x48, 0x70, 255};
color_t colorTuna = {0x34, 0x34, 0x48, 255};
color_t colorHacienda = {0x94, 0x80, 0x2C, 255};
color_t colorKumera = {0x84, 0x70, 0x24, 255};
color_t colorHimalaya = {0x74, 0x60, 0x1C, 255};
color_t colorChocolate = {0x84, 0x44, 0, 255};
color_t colorNutmeg = {0x70, 0x38, 0, 255};
color_t colorBracken = {0x48, 0x24, 0, 255};
color_t colorTeal = {0, 0x84, 0x84, 255};
color_t colorSkobeloff = {0, 0x70, 0x70, 255};
color_t colorDeepJungleGreen = {0, 0x48, 0x48, 255};
color_t colorSkin = { 0xF4, 0x94, 0x4C, 255 };
color_t colorDarkSkin = { 0x93, 0x5D, 0x37, 255 };
color_t colorAsianSkin = { 0xFF, 0xCC, 0x99, 255 };
color_t colorSkin = {0xF4, 0x94, 0x4C, 255};
color_t colorDarkSkin = {0x93, 0x5D, 0x37, 255};
color_t colorAsianSkin = {0xFF, 0xCC, 0x99, 255};
color_t colorLightBlue = { 80, 80, 160, 255 };
color_t colorLightBlue = {80, 80, 160, 255};
color_t colorSelectedBG = {0, 255, 255, 64};
color_t ColorMult(color_t c, color_t m)
{
@@ -95,22 +96,22 @@ color_t ColorMult(color_t c, color_t m)
}
color_t ColorAlphaBlend(color_t a, color_t b)
{
a.r = (uint8_t)(((int)a.r*(255 - b.a) + (int)b.r*b.a)/255);
a.g = (uint8_t)(((int)a.g*(255 - b.a) + (int)b.g*b.a)/255);
a.b = (uint8_t)(((int)a.b*(255 - b.a) + (int)b.b*b.a)/255);
a.r = (uint8_t)(((int)a.r * (255 - b.a) + (int)b.r * b.a) / 255);
a.g = (uint8_t)(((int)a.g * (255 - b.a) + (int)b.g * b.a) / 255);
a.b = (uint8_t)(((int)a.b * (255 - b.a) + (int)b.b * b.a) / 255);
a.a = 255;
return a;
}
HSV tintNone = { -1.0, 1.0, 1.0 };
HSV tintRed = { 0.0, 1.0, 1.0 };
HSV tintYellow = { 60.0, 1.0, 1.0 };
HSV tintGreen = { 120.0, 1.0, 1.0 };
HSV tintCyan = { 180.0, 1.0, 1.0 };
HSV tintPoison = { 120.0, 0.33, 2.0 };
HSV tintGray = { -1.0, 0.0, 1.0 };
HSV tintPurple = { 300, 1.0, 1.0 };
HSV tintDarker = { -1.0, 1.0, 0.75 };
HSV tintNone = {-1.0, 1.0, 1.0};
HSV tintRed = {0.0, 1.0, 1.0};
HSV tintYellow = {60.0, 1.0, 1.0};
HSV tintGreen = {120.0, 1.0, 1.0};
HSV tintCyan = {180.0, 1.0, 1.0};
HSV tintPoison = {120.0, 0.33, 2.0};
HSV tintGray = {-1.0, 0.0, 1.0};
HSV tintPurple = {300, 1.0, 1.0};
HSV tintDarker = {-1.0, 1.0, 0.75};
color_t ColorTint(color_t c, HSV hsv)
{
@@ -147,7 +148,7 @@ color_t ColorTint(color_t c, HSV hsv)
q = (uint8_t)CLAMP(vComponent * (1.0 - (hsv.s * ff)), 0, 255);
t = (uint8_t)CLAMP(vComponent * (1.0 - (hsv.s * (1.0 - ff))), 0, 255);
switch(i)
switch (i)
{
case 0:
out.r = vComponent;
@@ -187,9 +188,12 @@ color_t ColorTint(color_t c, HSV hsv)
{
// Just set saturation and value
// Use weighted average to shift components towards grey for saturation
out.r = (uint8_t)CLAMP(hsv.v * (vAvg*(1.0-hsv.s) + hsv.s*c.r), 0, 255);
out.g = (uint8_t)CLAMP(hsv.v * (vAvg*(1.0-hsv.s) + hsv.s*c.g), 0, 255);
out.b = (uint8_t)CLAMP(hsv.v * (vAvg*(1.0-hsv.s) + hsv.s*c.b), 0, 255);
out.r = (uint8_t)CLAMP(
hsv.v * (vAvg * (1.0 - hsv.s) + hsv.s * c.r), 0, 255);
out.g = (uint8_t)CLAMP(
hsv.v * (vAvg * (1.0 - hsv.s) + hsv.s * c.g), 0, 255);
out.b = (uint8_t)CLAMP(
hsv.v * (vAvg * (1.0 - hsv.s) + hsv.s * c.b), 0, 255);
}
out.a = c.a;
return out;
@@ -203,7 +207,7 @@ bool HSVEquals(const HSV a, const HSV b)
{
const double epsilon = 0.000001;
return fabs(a.h - b.h) < epsilon && fabs(a.s - b.s) < epsilon &&
fabs(a.v - b.v) < epsilon;
fabs(a.v - b.v) < epsilon;
}
color_t StrColor(const char *s)

View File

@@ -2,7 +2,7 @@
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2017 Cong Xu
Copyright (c) 2013-2017, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -90,6 +90,7 @@ extern color_t colorDarkSkin;
extern color_t colorAsianSkin;
extern color_t colorLightBlue;
extern color_t colorSelectedBG;
color_t ColorMult(color_t c, color_t m);
color_t ColorAlphaBlend(color_t a, color_t b);

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2016, 2018-2022 Cong Xu
Copyright (c) 2013-2016, 2018-2022, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -68,7 +68,7 @@
#include "pics.h"
#include "texture.h"
//#define DEBUG_DRAW_HITBOXES
// #define DEBUG_DRAW_HITBOXES
// Three types of tile drawing, based on line of sight:
// Unvisited: black
@@ -169,6 +169,9 @@ static void DrawObjectiveHighlights(
static void DrawChatters(
DrawBuffer *b, const struct vec2i offset, const Tile *t,
const struct vec2i pos, const bool useFog);
static void DrawPickupMenus(
DrawBuffer *b, const struct vec2i offset, const Tile *t,
const struct vec2i pos, const bool useFog);
static void DrawExtra(
DrawBuffer *b, struct vec2i offset, const DrawBufferArgs *args);
@@ -189,6 +192,8 @@ void DrawBufferDraw(
DrawTiles(b, offset, DrawObjectiveHighlights);
// Draw actor chatter
DrawTiles(b, offset, DrawChatters);
// Draw actor pickup menus
DrawTiles(b, offset, DrawPickupMenus);
}
// Draw editor-only things
DrawExtra(b, offset, args);
@@ -398,6 +403,79 @@ static void DrawChatters(
CA_FOREACH_END()
}
static void DrawPickupMenu(
DrawBuffer *b, const TActor *a, const struct vec2i offset);
static void DrawPickupMenus(
DrawBuffer *b, const struct vec2i offset, const Tile *t,
const struct vec2i pos, const bool useFog)
{
UNUSED(pos);
CA_FOREACH(ThingId, tid, t->things)
// Draw the items that are in LOS
if (t->outOfSight || ColorEquals(GetLOSMask(t, useFog), colorTransparent))
{
continue;
}
const Thing *ti = ThingIdGetThing(tid);
if (ti->kind != KIND_CHARACTER)
{
continue;
}
const TActor *a = CArrayGet(&gActors, ti->id);
// Draw pickup menu
if (!a->pickupMenu.pickup || !ActorIsLocalPlayer(a->uid))
{
continue;
}
DrawPickupMenu(b, a, offset);
CA_FOREACH_END()
}
static void DrawPickupMenu(
DrawBuffer *b, const TActor *a, const struct vec2i offset)
{
// Calculate width and height of menu and draw centered on the actor
// TODO: may be expensive to do per-frame; cache somewhere?
struct vec2i size = svec2i_zero();
struct vec2i ssize = FontStrSize(a->pickupMenu.effect->u.Menu.Text);
size.x = MAX(size.x, ssize.x);
size.y += ssize.y;
size.y += FontH(); // Separator
CA_FOREACH(const PickupMenuItem, m, a->pickupMenu.effect->u.Menu.Items)
ssize = FontStrSize(m->Text);
size.x = MAX(size.x, ssize.x);
size.y += ssize.y;
CA_FOREACH_END()
struct vec2i pos = svec2i(
(int)a->thing.Pos.x - b->xTop + offset.x - size.x / 2,
(int)a->thing.Pos.y - b->yTop + offset.y - size.y / 2);
const int startX = pos.x;
// Draw box bg with a bit of padding
const color_t cbg = {64, 64, 64, 128};
DrawRectangle(
b->g, svec2i_subtract(pos, svec2i(2, 2)),
svec2i_add(size, svec2i(4, 4)), cbg, true);
pos = FontStr(a->pickupMenu.effect->u.Menu.Text, pos);
pos.x = startX;
pos.y += FontH() * 2; // Separator
CA_FOREACH(const PickupMenuItem, m, a->pickupMenu.effect->u.Menu.Items)
const bool selected = _ca_index == a->pickupMenu.index;
if (selected)
{
// Add 1px padding
const struct vec2i bgPos = svec2i_subtract(pos, svec2i_one());
const struct vec2i bgSize =
svec2i_add(svec2i(size.x, FontH()), svec2i(2, 2));
DrawRectangle(b->g, bgPos, bgSize, colorSelectedBG, true);
}
pos = FontStrMask(m->Text, pos, selected ? colorRed : colorWhite);
pos.x = startX;
pos.y += FontH();
CA_FOREACH_END()
}
static void DrawThing(DrawBuffer *b, const Thing *t, const struct vec2i offset)
{
const struct vec2i picPos = svec2i_add(
@@ -419,7 +497,7 @@ static void DrawThing(DrawBuffer *b, const Thing *t, const struct vec2i offset)
}
else if (t->kind == KIND_CHARACTER)
{
TActor *a = CArrayGet(&gActors, t->id);
const TActor *a = CArrayGet(&gActors, t->id);
ActorPics pics = GetCharacterPicsFromActor(a);
DrawActorPics(&pics, picPos, Rect2iZero());
// Draw weapon indicators

View File

@@ -27,9 +27,6 @@
*/
#include "pickup.h"
#include "ammo.h"
#include "game_events.h"
#include "gamedata.h"
#include "json_utils.h"
#include "map.h"
#include "net_util.h"
@@ -130,272 +127,6 @@ void PickupsUpdate(CArray *pickups, const int ticks)
CA_FOREACH_END()
}
static bool TreatAsGunPickup(const PickupEffect *pe, const TActor *a);
static bool TryPickupAmmo(TActor *a, const Pickup *p, const PickupEffect *pe);
static bool TryPickupGun(
TActor *a, const PickupEffect *pe, const bool pickupAll,
const char **sound);
void PickupPickup(TActor *a, Pickup *p, const bool pickupAll)
{
if (p->PickedUp)
return;
CASSERT(a->PlayerUID >= 0, "NPCs cannot pickup");
// can always pickup effect-less pickups
bool canPickup = p->class->Effects.size == 0;
const char *sound = p->class->Sound;
const struct vec2 actorPos = a->thing.Pos;
CA_FOREACH(const PickupEffect, pe, p->class->Effects)
switch (pe->Type)
{
case PICKUP_JEWEL: {
canPickup = true;
GameEvent e = GameEventNew(GAME_EVENT_SCORE);
e.u.Score.PlayerUID = a->PlayerUID;
e.u.Score.Score = pe->u.Score;
GameEventsEnqueue(&gGameEvents, e);
e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
e.u.AddParticle.Class =
StrParticleClass(&gParticleClasses, "score_text");
e.u.AddParticle.ActorUID = a->uid;
e.u.AddParticle.Pos = p->thing.Pos;
e.u.AddParticle.DZ = 3;
if (gCampaign.Setting.Ammo)
{
sprintf(e.u.AddParticle.Text, "$%d", pe->u.Score);
}
else
{
sprintf(e.u.AddParticle.Text, "+%d", pe->u.Score);
}
GameEventsEnqueue(&gGameEvents, e);
UpdateMissionObjective(
&gMission, p->thing.flags, OBJECTIVE_COLLECT, 1);
}
break;
case PICKUP_HEALTH:
// Don't pick up unless taken damage
if (a->health < ActorGetMaxHeal(a, pe->u.Heal.ExceedMax))
{
canPickup = true;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_HEAL);
e.u.Heal.UID = a->uid;
e.u.Heal.PlayerUID = a->PlayerUID;
e.u.Heal.Amount = pe->u.Heal.Amount;
e.u.Heal.ExceedMax = pe->u.Heal.ExceedMax;
e.u.Heal.IsRandomSpawned = p->IsRandomSpawned;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_AMMO: // fallthrough
case PICKUP_GUN:
if (TreatAsGunPickup(pe, a))
{
canPickup = TryPickupGun(a, pe, pickupAll, &sound) || canPickup;
}
else
{
canPickup = TryPickupAmmo(a, p, pe) || canPickup;
}
break;
case PICKUP_KEYCARD: {
canPickup = true;
GameEvent e = GameEventNew(GAME_EVENT_ADD_KEYS);
e.u.AddKeys.KeyFlags = pe->u.Keys;
e.u.AddKeys.Pos = Vec2ToNet(actorPos);
GameEventsEnqueue(&gGameEvents, e);
if (sound == NULL)
{
sound = "key";
}
}
break;
case PICKUP_SHOW_MAP: {
canPickup = true;
GameEvent e = GameEventNew(GAME_EVENT_EXPLORE_TILES);
e.u.ExploreTiles.Runs_count = 1;
e.u.ExploreTiles.Runs[0].Run = gMap.Size.x * gMap.Size.y;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_LIVES: {
canPickup = true;
GameEvent e = GameEventNew(GAME_EVENT_PLAYER_ADD_LIVES);
e.u.PlayerAddLives.UID = a->PlayerUID;
e.u.PlayerAddLives.Lives = pe->u.Lives;
GameEventsEnqueue(&gGameEvents, e);
}
break;
case PICKUP_SOUND:
canPickup = true;
sound = pe->u.Sound;
break;
case PICKUP_MENU:
// TODO: manual activate menu, set player menu
break;
default:
CASSERT(false, "unexpected pickup type");
break;
}
CA_FOREACH_END()
if (canPickup)
{
if (sound != NULL)
{
GameEvent es = GameEventNew(GAME_EVENT_SOUND_AT);
strcpy(es.u.SoundAt.Sound, sound);
es.u.SoundAt.Pos = Vec2ToNet(actorPos);
GameEventsEnqueue(&gGameEvents, es);
}
GameEvent e = GameEventNew(GAME_EVENT_REMOVE_PICKUP);
e.u.RemovePickup.UID = p->UID;
e.u.RemovePickup.SpawnerUID = p->SpawnerUID;
GameEventsEnqueue(&gGameEvents, e);
// Prevent multiple pickups by marking
p->PickedUp = true;
a->PickupAll = false;
}
}
static bool HasGunUsingAmmo(const TActor *a, const int ammoId);
static bool TreatAsGunPickup(const PickupEffect *pe, const TActor *a)
{
// Grenades can also be gun pickups; treat as gun pickup if the player
// doesn't have its ammo
switch (pe->Type)
{
case PICKUP_AMMO:
if (!HasGunUsingAmmo(a, pe->u.Ammo.Id))
{
const Ammo *ammo = AmmoGetById(&gAmmo, pe->u.Ammo.Id);
if (ammo->DefaultGun)
{
return true;
}
}
return false;
case PICKUP_GUN: {
const WeaponClass *wc = IdWeaponClass(pe->u.GunId);
return wc->Type != GUNTYPE_GRENADE ||
!HasGunUsingAmmo(a, wc->u.Normal.AmmoId);
}
default:
CASSERT(false, "unexpected pickup type");
return false;
}
}
static bool HasGunUsingAmmo(const TActor *a, const int ammoId)
{
for (int i = 0; i < MAX_WEAPONS; i++)
{
const WeaponClass *wc = a->guns[i].Gun;
if (wc == NULL)
continue;
for (int j = 0; j < WeaponClassNumBarrels(wc); j++)
{
if (WC_BARREL_ATTR(*wc, AmmoId, j) == ammoId)
{
return true;
}
}
}
return false;
}
static bool TryPickupAmmo(TActor *a, const Pickup *p, const PickupEffect *pe)
{
// Don't pickup if not using ammo
if (!gCampaign.Setting.Ammo)
{
return false;
}
// Don't pickup if ammo full
const int ammoId = pe->Type == PICKUP_AMMO
? (int)pe->u.Ammo.Id
: IdWeaponClass(pe->u.GunId)->u.Normal.AmmoId;
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int amount =
pe->Type == PICKUP_AMMO ? (int)pe->u.Ammo.Amount : ammo->Amount;
const int current = *(int *)CArrayGet(&a->ammo, ammoId);
if (ammo->Max > 0 && current >= ammo->Max)
{
return false;
}
// Take ammo
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD_AMMO);
e.u.AddAmmo.UID = a->uid;
e.u.AddAmmo.PlayerUID = a->PlayerUID;
e.u.AddAmmo.Ammo.Id = ammoId;
e.u.AddAmmo.Ammo.Amount = amount;
e.u.AddAmmo.IsRandomSpawned = p->IsRandomSpawned;
// Note: receiving end will prevent ammo from exceeding max
GameEventsEnqueue(&gGameEvents, e);
return true;
}
static bool TryPickupGun(
TActor *a, const PickupEffect *pe, const bool pickupAll,
const char **sound)
{
// Guns can only be picked up manually
if (!pickupAll)
{
return false;
}
// When picking up a gun, the actor always ends up with it equipped, but:
// - If the player already has the gun:
// - Switch to the same gun and drop the same gun
// - If the player doesn't have the gun:
// - If the player has an empty slot, pickup the gun into that slot
// - If the player doesn't have an empty slot, replace the current gun,
// dropping it in the process
const WeaponClass *wc =
pe->Type == PICKUP_GUN
? IdWeaponClass(pe->u.GunId)
: StrWeaponClass(AmmoGetById(&gAmmo, pe->u.Ammo.Id)->DefaultGun);
ActorPickupGun(a, wc);
// If the player has less ammo than the default amount,
// replenish up to this amount
// TODO: support multi gun
const int ammoId = WC_BARREL_ATTR(*wc, AmmoId, 0);
if (ammoId >= 0)
{
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int ammoDeficit = ammo->Amount * AMMO_STARTING_MULTIPLE -
*(int *)CArrayGet(&a->ammo, ammoId);
if (ammoDeficit > 0)
{
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD_AMMO);
e.u.AddAmmo.UID = a->uid;
e.u.AddAmmo.PlayerUID = a->PlayerUID;
e.u.AddAmmo.Ammo.Id = ammoId;
e.u.AddAmmo.Ammo.Amount = ammoDeficit;
e.u.AddAmmo.IsRandomSpawned = false;
GameEventsEnqueue(&gGameEvents, e);
// Also play an ammo pickup sound
*sound = ammo->Sound;
}
}
return true;
}
bool PickupIsManual(const Pickup *p)
{
if (p->PickedUp)
@@ -413,6 +144,8 @@ bool PickupIsManual(const Pickup *p)
}
}
break;
case PICKUP_MENU:
return true;
default:
break;
}

View File

@@ -1,33 +1,32 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2015, Cong Xu
All rights reserved.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2015, Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "actors.h"
#include "pic.h"
#include "pickup_class.h"
#include "tile.h"
@@ -46,7 +45,7 @@ typedef struct
int SpawnerUID;
} Pickup;
extern CArray gPickups; // of Pickup
extern CArray gPickups; // of Pickup
void PickupsInit(void);
void PickupsTerminate(void);
@@ -57,7 +56,6 @@ void PickupDestroy(const int uid);
void PickupsUpdate(CArray *pickups, const int ticks);
void PickupPickup(TActor *a, Pickup *p, const bool pickupAll);
// Check if the pickup needs to be picked up manually
bool PickupIsManual(const Pickup *p);

View File

@@ -563,3 +563,30 @@ int PickupClassGetKeys(const PickupClass *p)
CA_FOREACH_END()
return 0;
}
const char *PickupClassGetName(const PickupClass *p)
{
const char *pickupName = NULL;
bool hasMultipleEffects = false;
CA_FOREACH(const PickupEffect, pe, p->Effects)
switch (pe->Type)
{
case PICKUP_AMMO:
hasMultipleEffects = pickupName != NULL;
pickupName = AmmoGetById(&gAmmo, pe->u.Ammo.Id)->Name;
break;
case PICKUP_GUN:
hasMultipleEffects = pickupName != NULL;
pickupName = IdWeaponClass(pe->u.GunId)->name;
break;
default:
break;
}
CA_FOREACH_END()
if (pickupName == NULL || hasMultipleEffects)
{
// fallback to using the raw class name
pickupName = p->Name;
}
return pickupName;
}

View File

@@ -124,6 +124,7 @@ PickupClass *IntScorePickupClass(const int i);
bool PickupClassHasAmmoEffect(const PickupClass *p);
bool PickupClassHasKeyEffect(const PickupClass *p);
int PickupClassGetKeys(const PickupClass *p);
const char *PickupClassGetName(const PickupClass *p);
// Score for picking up an objective
#define PICKUP_SCORE 10

View File

@@ -120,12 +120,11 @@ static void DrawEquipSlot(
}
else
{
const color_t bg = {0, 255, 255, 64};
// Add 1px padding
const struct vec2i bgPos = svec2i_subtract(pos, svec2i_one());
const struct vec2i bgSize =
svec2i(EQUIP_MENU_WIDTH / 2 + 2, h + 2);
DrawRectangle(g, bgPos, bgSize, bg, true);
DrawRectangle(g, bgPos, bgSize, colorSelectedBG, true);
color = colorRed;
}

View File

@@ -468,11 +468,10 @@ struct vec2i DisplayMenuItem(
}
if (selected)
{
const color_t bg = {0, 255, 255, 64};
// Add 1px padding
const struct vec2i bgPos = svec2i_subtract(bounds.Pos, svec2i_one());
const struct vec2i bgSize = svec2i_add(bounds.Size, svec2i(2, 2));
DrawRectangle(g, bgPos, bgSize, bg, true);
DrawRectangle(g, bgPos, bgSize, colorSelectedBG, true);
return FontStrMask(s, bounds.Pos, colorRed);
}
if (!ColorEquals(color, colorTransparent))

View File

@@ -249,8 +249,7 @@ static void DrawUtilMenuItem(
color_t color = colorWhite;
if (selected && data->Active)
{
const color_t cbg = {0, 255, 255, 64};
DrawRectangle(g, bgPos, bgSize, cbg, true);
DrawRectangle(g, bgPos, bgSize, colorSelectedBG, true);
color = colorRed;
}

View File

@@ -350,8 +350,7 @@ static void DrawGun(
const color_t mask = color;
if (selected && data->Active)
{
const color_t cbg = {0, 255, 255, 64};
DrawRectangle(g, bgPos, bgSize, cbg, true);
DrawRectangle(g, bgPos, bgSize, colorSelectedBG, true);
color = colorRed;
}
else if (equipped)