Track bullet source weapon and calculate fav weapon, acc in high scores #151

This commit is contained in:
Cong
2025-03-05 23:06:35 +11:00
parent 2da35d5de8
commit 3eba2e8ec8
21 changed files with 581 additions and 209 deletions

View File

@@ -111,6 +111,7 @@ set(CDOGS_SOURCES
vector.c
weapon.c
weapon_class.c
weapon_usage.c
window_context.c
XGetopt.c
yajl_utils.c)
@@ -225,6 +226,7 @@ set(CDOGS_HEADERS
vector.h
weapon.h
weapon_class.h
weapon_usage.h
window_context.h
XGetopt.h
yajl_utils.h)

View File

@@ -114,6 +114,7 @@ void OnGunFire(const NGunFire gf, SoundDevice *sd)
wc->u.Normal.ElevationLow, wc->u.Normal.ElevationHigh);
ab.u.AddBullet.Flags = gf.Flags;
ab.u.AddBullet.ActorUID = gf.ActorUID;
strcpy(ab.u.AddBullet.Gun, wc->name);
CA_FOREACH(const BulletClass *, bc, wc->u.Normal.Bullets)
ab.u.AddBullet.UID = MobObjsObjsGetNextUID();

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2024 Cong Xu
Copyright (c) 2013-2025 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -143,7 +143,8 @@ void UpdateActorState(TActor *actor, int ticks)
actor->petrified = MAX(0, actor->petrified - ticks);
actor->confused = MAX(0, actor->confused - ticks);
// Reset accumulated damage if FPS_FRAMELIMIT passed since taking damage
// Reset accumulated damage if FPS_FRAMELIMIT passed since taking
// damage
actor->damageCooldownTicks += ticks;
if (actor->accumulatedDamage)
{
@@ -714,8 +715,8 @@ static void CheckRescue(const TActor *a)
if (a->PlayerUID < 0)
return;
// Check an area slightly bigger than the actor's size for rescue
// objectives
// Check an area slightly bigger than the actor's size for rescue
// objectives
#define RESCUE_CHECK_PAD 2
const CollisionParams params = {
THING_IMPASSABLE, CalcCollisionTeam(true, a),
@@ -2181,16 +2182,14 @@ static void ActorTakeSpecialDamage(
}
}
static void ActorTakeHit(
TActor *actor, const int flags, const int sourceUID,
const special_damage_e damage, const int specialTicks);
static void ActorTakeHit(TActor *actor, const NThingDamage d);
void ActorHit(const NThingDamage d)
{
TActor *a = ActorGetByUID(d.UID);
if (!a->isInUse)
return;
ActorTakeHit(a, d.Flags, d.SourceActorUID, d.Special, d.SpecialTicks);
ActorTakeHit(a, d);
if (d.Power > 0)
{
DamageActor(a, d.Power, d.SourceActorUID);
@@ -2245,23 +2244,31 @@ void ActorHit(const NThingDamage d)
}
}
static void ActorTakeHit(
TActor *actor, const int flags, const int sourceUID,
const special_damage_e damage, const int specialTicks)
static void ActorTakeHit(TActor *actor, const NThingDamage d)
{
// Wake up if this is an AI
if (!gCampaign.IsClient)
{
AIWake(actor, 1);
}
const TActor *source = ActorGetByUID(sourceUID);
const TActor *source = ActorGetByUID(d.SourceActorUID);
const int playerUID = source != NULL ? source->PlayerUID : -1;
if (ActorIsInvulnerable(
actor, flags, playerUID, gCampaign.Entry.Mode, damage))
actor, d.Flags, playerUID, gCampaign.Entry.Mode, d.Special))
{
return;
}
ActorTakeSpecialDamage(actor, damage, specialTicks);
ActorTakeSpecialDamage(actor, d.Special, d.SpecialTicks);
// Update hits
// TODO: correct accuracy for persistent bullets
if (!gCampaign.IsClient && playerUID >= 0 &&
strlen(d.SourceWeaponClassName) > 0)
{
PlayerData *p = PlayerDataGetByUID(playerUID);
const WeaponClass *wc = StrWeaponClass(d.SourceWeaponClassName);
WeaponUsagesUpdate(p->WeaponUsages, wc, 0, 1);
}
}
bool ActorIsInvulnerable(

View File

@@ -644,8 +644,8 @@ static void OnHit(HitItemData *data, Thing *target)
data->HitType = GetHitType(target, data->Obj, &targetUID);
const TActor *source = ActorGetByUID(data->Obj->ActorUID);
Damage(
data->Obj->thing.Vel, data->Obj->bulletClass, data->Obj->flags, source,
target->kind, targetUID);
data->Obj->thing.Vel, data->Obj->bulletClass, data->Obj->weapon,
data->Obj->flags, source, target->kind, targetUID);
if (target->SoundLock <= 0)
{
target->SoundLock += SOUND_LOCK_THING;
@@ -1084,6 +1084,8 @@ void BulletAdd(const NAddBullet add)
obj->flags |= FLAGS_HURTALWAYS;
}
obj->weapon = StrWeaponClass(add.Gun);
obj->isInUse = true;
obj->thing.drawFunc = NULL;
obj->thing.drawData.MobObjId = i;
@@ -1091,6 +1093,18 @@ void BulletAdd(const NAddBullet add)
obj->thing.CPicFunc = BulletDraw;
obj->thing.ShadowSize = obj->bulletClass->ShadowSize;
MapTryMoveThing(&gMap, &obj->thing, pos);
// Update shots
if (!gCampaign.IsClient)
{
const TActor *source = ActorGetByUID(add.ActorUID);
const int playerUID = source != NULL ? source->PlayerUID : -1;
PlayerData *p = PlayerDataGetByUID(playerUID);
if (p)
{
WeaponUsagesUpdate(p->WeaponUsages, obj->weapon, 1, 0);
}
}
}
void BulletBounce(const NBulletBounce bb)

View File

@@ -1,50 +1,50 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This file incorporates work covered by the following copyright and
permission notice:
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2015, 2018-2019 Cong Xu
All rights reserved.
Copyright (c) 2013-2015, 2018-2019 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 "damage.h"
@@ -58,7 +58,6 @@
// make blood spurt further
#define MELEE_VEL_SCALE 40.0f
bool CanHitCharacter(const int flags, const int uid, const TActor *actor)
{
// Don't let players hurt themselves
@@ -70,15 +69,16 @@ bool CanHitCharacter(const int flags, const int uid, const TActor *actor)
}
bool CanDamageCharacter(
const int flags, const TActor *source,
const TActor *target, const special_damage_e special)
const int flags, const TActor *source, const TActor *target,
const special_damage_e special)
{
if (!CanHitCharacter(flags, source ? source->uid : -1, target))
{
return false;
}
return !ActorIsInvulnerable(
target, flags, source ? source->PlayerUID : -1, gCampaign.Entry.Mode, special);
target, flags, source ? source->PlayerUID : -1, gCampaign.Entry.Mode,
special);
}
static void TrackKills(PlayerData *pd, const TActor *victim);
@@ -87,17 +87,24 @@ void DamageActor(TActor *victim, const int power, const int sourceActorUID)
const int startingHealth = victim->health;
InjureActor(victim, power);
const TActor *source = ActorGetByUID(sourceActorUID);
if (startingHealth > 0 && victim->health <= 0 &&
source != NULL && source->PlayerUID >= 0)
// Track kills and hits
if (source != NULL)
{
TrackKills(PlayerDataGetByUID(source->PlayerUID), victim);
PlayerData *pd = PlayerDataGetByUID(source->PlayerUID);
if (pd != NULL)
{
if (startingHealth > 0 && victim->health <= 0)
{
TrackKills(pd, victim);
}
}
}
}
static void TrackKills(PlayerData *pd, const TActor *victim)
{
if (!IsPVP(gCampaign.Entry.Mode) &&
(victim->PlayerUID >= 0 ||
(victim->flags & (FLAGS_GOOD_GUY | FLAGS_PENALTY))))
(victim->flags & (FLAGS_GOOD_GUY | FLAGS_PENALTY))))
{
pd->Stats.Friendlies++;
pd->Totals.Friendlies++;
@@ -117,18 +124,18 @@ static void TrackKills(PlayerData *pd, const TActor *victim)
void DamageMelee(const NActorMelee m)
{
const TActor *a = ActorGetByUID(m.UID);
if (!a->isInUse) return;
if (!a->isInUse)
return;
const BulletClass *b = StrBulletClass(m.BulletClass);
if ((HitType)m.HitType != HIT_NONE &&
HasHitSound((ThingKind)m.TargetKind, m.TargetUID,
SPECIAL_NONE, false))
HasHitSound((ThingKind)m.TargetKind, m.TargetUID, SPECIAL_NONE, false))
{
PlayHitSound(b, (HitType)m.HitType, a->Pos);
}
if (!gCampaign.IsClient)
{
const Thing *target = ThingGetByUID(
(ThingKind)m.TargetKind, m.TargetUID);
const Thing *target =
ThingGetByUID((ThingKind)m.TargetKind, m.TargetUID);
const struct vec2 vel = svec2_scale(
svec2_add(
svec2_normalize(svec2_subtract(target->Pos, a->Pos)),
@@ -137,6 +144,7 @@ void DamageMelee(const NActorMelee m)
RAND_FLOAT(-MELEE_SPREAD_FACTOR, MELEE_SPREAD_FACTOR))),
MELEE_VEL_SCALE);
Damage(
vel, b, a->flags, a, (ThingKind)m.TargetKind, m.TargetUID);
vel, b, NULL, // Don't track hits/accuracy for melee weapons
a->flags, a, (ThingKind)m.TargetKind, m.TargetUID);
}
}

View File

@@ -56,6 +56,28 @@ bool NetDecode(ENetPacket *packet, void *dest, const pb_msgdesc_t *fields)
return status;
}
typedef struct
{
map_t src;
NPlayerData *d;
} AddWeaponUsageData;
static int AddWeaponUsage(any_t data, any_t key)
{
AddWeaponUsageData *aData = (AddWeaponUsageData *)data;
// TODO: copy weapon usage
NWeaponUsage *w;
int error = hashmap_get(aData->src, (char *)key, (any_t *)&w);
if (error != MAP_OK)
{
return error;
}
NWeaponUsage *n = &aData->d->WeaponUsages[aData->d->WeaponUsages_count];
strcpy(n->Weapon, (char *)key);
n->Shots = w->Shots;
n->Hits = w->Hits;
aData->d->WeaponUsages_count++;
return MAP_OK;
}
NPlayerData NMakePlayerData(const PlayerData *p)
{
NPlayerData d = NPlayerData_init_default;
@@ -85,6 +107,12 @@ NPlayerData NMakePlayerData(const PlayerData *p)
{
strcpy(d.Weapons[i], p->guns[i] != NULL ? p->guns[i]->name : "");
}
d.WeaponUsages_count = 0;
AddWeaponUsageData aData;
aData.src = p->WeaponUsages;
aData.d = &d;
hashmap_iterate_keys(p->WeaponUsages, AddWeaponUsage, &aData);
d.Lives = p->Lives;
d.Stats = p->Stats;
d.Totals = p->Totals;

View File

@@ -38,7 +38,7 @@
#include "map.h"
#include "player.h"
#define NET_PROTOCOL_VERSION 15
#define NET_PROTOCOL_VERSION 16
// Messages

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2021, 2024 Cong Xu
Copyright (c) 2013-2021, 2024-2025 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -133,6 +133,18 @@ void DamageObject(const NThingDamage d)
EmitterStart(&em, &ap);
}
}
// Update hits
// TODO: correct accuracy for persistent bullets
const TActor *source = ActorGetByUID(d.SourceActorUID);
const int playerUID = source != NULL ? source->PlayerUID : -1;
if (!gCampaign.IsClient && playerUID >= 0 &&
strlen(d.SourceWeaponClassName) > 0)
{
PlayerData *p = PlayerDataGetByUID(playerUID);
const WeaponClass *wc = StrWeaponClass(d.SourceWeaponClassName);
WeaponUsagesUpdate(p->WeaponUsages, wc, 0, 1);
}
}
static void AddPickupAtObject(const TObject *o, const PickupType type)
@@ -301,25 +313,27 @@ bool HasHitSound(
static void DoDamageThing(
const ThingKind targetKind, const int targetUID, const TActor *source,
const int flags, const BulletClass *bullet, const bool canDamage,
const struct vec2 hitVector);
const int flags, const BulletClass *bullet, const WeaponClass *weapon,
const bool canDamage, const struct vec2 hitVector);
static void DoDamageCharacter(
const TActor *actor, const TActor *source, const struct vec2 hitVector,
const BulletClass *bullet, const int flags);
const BulletClass *bullet, const WeaponClass *weapon, const int flags);
void Damage(
const struct vec2 hitVector, const BulletClass *bullet, const int flags,
const TActor *source, const ThingKind targetKind, const int targetUID)
const struct vec2 hitVector, const BulletClass *bullet,
const WeaponClass *weapon, const int flags, const TActor *source,
const ThingKind targetKind, const int targetUID)
{
switch (targetKind)
{
case KIND_CHARACTER: {
const TActor *actor = ActorGetByUID(targetUID);
DoDamageCharacter(actor, source, hitVector, bullet, flags);
DoDamageCharacter(actor, source, hitVector, bullet, weapon, flags);
}
break;
case KIND_OBJECT:
DoDamageThing(
targetKind, targetUID, source, flags, bullet, true, hitVector);
targetKind, targetUID, source, flags, bullet, weapon, true,
hitVector);
break;
default:
CASSERT(false, "cannot damage tile item kind");
@@ -328,8 +342,8 @@ void Damage(
}
static void DoDamageThing(
const ThingKind targetKind, const int targetUID, const TActor *source,
const int flags, const BulletClass *bullet, const bool canDamage,
const struct vec2 hitVector)
const int flags, const BulletClass *bullet, const WeaponClass *weapon,
const bool canDamage, const struct vec2 hitVector)
{
GameEvent e = GameEventNew(GAME_EVENT_THING_DAMAGE);
e.u.ThingDamage.UID = targetUID;
@@ -342,11 +356,15 @@ static void DoDamageThing(
e.u.ThingDamage.Power = 0;
}
e.u.ThingDamage.Vel = Vec2ToNet(hitVector);
if (weapon)
{
strcpy(e.u.ThingDamage.SourceWeaponClassName, weapon->name);
}
GameEventsEnqueue(&gGameEvents, e);
}
static void DoDamageCharacter(
const TActor *actor, const TActor *source, const struct vec2 hitVector,
const BulletClass *bullet, const int flags)
const BulletClass *bullet, const WeaponClass *weapon, const int flags)
{
// Create events: hit, damage, score
CASSERT(actor->isInUse, "Cannot damage nonexistent player");
@@ -372,7 +390,7 @@ static void DoDamageCharacter(
CanDamageCharacter(flags, source, actor, bullet->Special.Effect);
DoDamageThing(
KIND_CHARACTER, actor->uid, source, flags, bullet, canDamage,
KIND_CHARACTER, actor->uid, source, flags, bullet, weapon, canDamage,
hitVector);
if (canDamage)

View File

@@ -1,50 +1,50 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This file incorporates work covered by the following copyright and
permission notice:
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2016, 2018-2019, 2021 Cong Xu
All rights reserved.
Copyright (c) 2013-2016, 2018-2019, 2021, 2025 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
@@ -54,15 +54,13 @@
#include "pics.h"
#include "vector.h"
// Bullet "height"
#define BULLET_Z 10
#define BULLET_Z 10
#define AMMO_SPAWNER_RESPAWN_TICKS (FPS_FRAMELIMIT * 30)
#define SHOT_IMPULSE_FACTOR 0.04f
typedef struct
{
int uid;
@@ -77,8 +75,8 @@ typedef struct
typedef struct MobileObject
{
int UID;
int ActorUID; // unique ID of actor that owns this object
// (prevent self collision)
int ActorUID; // unique ID of actor that owns this object
// (prevent self collision)
const BulletClass *bulletClass;
float z;
float dz;
@@ -87,22 +85,21 @@ typedef struct MobileObject
int flags;
Thing thing;
Emitter trail;
const WeaponClass *weapon; // Weapon that fired this
bool isInUse;
} TMobileObject;
typedef int (*MobObjUpdateFunc)(TMobileObject *, int);
extern CArray gMobObjs; // of TMobileObject
extern CArray gMobObjs; // of TMobileObject
extern CArray gObjs; // of TObject
bool CanHit(const BulletClass *b, const int flags, const int uid, const Thing *target);
bool CanHit(
const BulletClass *b, const int flags, const int uid, const Thing *target);
bool HasHitSound(
const ThingKind targetKind, const int targetUID,
const special_damage_e special, const bool allowFriendlyHitSound);
void Damage(
const struct vec2 hitVector,
const BulletClass *bullet,
const int flags,
const TActor *source,
const struct vec2 hitVector, const BulletClass *bullet,
const WeaponClass *weapon, const int flags, const TActor *source,
const ThingKind targetKind, const int targetUID);
void ObjsInit(void);

View File

@@ -70,7 +70,8 @@ static NamedPic *AddNamedPic(map_t pics, const char *name, const Pic *p);
static NamedSprites *AddNamedSprites(map_t sprites, const char *name);
static void AfterAdd(PicManager *pm);
static void PicManagerAdd(
map_t pics, map_t sprites, const char *name, SDL_Surface *imageIn, const bool isHD)
map_t pics, map_t sprites, const char *name, SDL_Surface *imageIn,
const bool isHD)
{
char buf[CDOGS_FILENAME_MAX];
const char *dot = strrchr(name, '.');
@@ -143,7 +144,8 @@ static void PicManagerAdd(
// which head part we are looking at
const char *subfolder = buf + strlen("chars/");
CharColorType headPartColor = CHAR_COLOR_HAIR;
if (strncmp("facehairs/", subfolder, strlen("facehairs/")) == 0)
if (strncmp("facehairs/", subfolder, strlen("facehairs/")) ==
0)
{
headPartColor = CHAR_COLOR_FACEHAIR;
}
@@ -151,7 +153,8 @@ static void PicManagerAdd(
{
headPartColor = CHAR_COLOR_HAT;
}
else if (strncmp("glasses/", subfolder, strlen("glasses/")) == 0)
else if (
strncmp("glasses/", subfolder, strlen("glasses/")) == 0)
{
headPartColor = CHAR_COLOR_GLASSES;
}
@@ -167,7 +170,8 @@ static void PicManagerAdd(
}
// Convert character color keyed color to
// greyscale + special alpha
const CharColorType colorType = CharColorTypeFromColor(c, headPartColor);
const CharColorType colorType =
CharColorTypeFromColor(c, headPartColor);
color_t converted = c;
if (colorType != CHAR_COLOR_COUNT)
{
@@ -250,7 +254,8 @@ void PicManagerLoadDir(
}
else
{
PicManagerLoadDir(pm, file.path, file.name, pics, sprites, isHD);
PicManagerLoadDir(
pm, file.path, file.name, pics, sprites, isHD);
}
}
}
@@ -282,10 +287,15 @@ static int MaybeAddKeyPicName(any_t data, any_t item);
static int MaybeAddDoorPicName(any_t data, any_t item);
static void AfterAdd(PicManager *pm)
{
FindStyleSprites(pm, &pm->headPartNames[HEAD_PART_HAIR], MaybeAddHairSpriteName);
FindStyleSprites(pm, &pm->headPartNames[HEAD_PART_FACEHAIR], MaybeAddFacehairSpriteName);
FindStyleSprites(pm, &pm->headPartNames[HEAD_PART_HAT], MaybeAddHatSpriteName);
FindStyleSprites(pm, &pm->headPartNames[HEAD_PART_GLASSES], MaybeAddGlassesSpriteName);
FindStyleSprites(
pm, &pm->headPartNames[HEAD_PART_HAIR], MaybeAddHairSpriteName);
FindStyleSprites(
pm, &pm->headPartNames[HEAD_PART_FACEHAIR],
MaybeAddFacehairSpriteName);
FindStyleSprites(
pm, &pm->headPartNames[HEAD_PART_HAT], MaybeAddHatSpriteName);
FindStyleSprites(
pm, &pm->headPartNames[HEAD_PART_GLASSES], MaybeAddGlassesSpriteName);
FindStylePics(pm, &pm->wallStyleNames, MaybeAddWallPicName);
FindStylePics(pm, &pm->tileStyleNames, MaybeAddTilePicName);
FindStylePics(pm, &pm->exitStyleNames, MaybeAddExitPicName);
@@ -372,29 +382,33 @@ static void MaybeAddStyleName(
CSTRDUP(s, buf);
CArrayPushBack(styleNames, &s);
}
static int MaybeAddHeadPartSpriteName(any_t data, any_t item, const HeadPart hp, const char *path)
static int MaybeAddHeadPartSpriteName(
any_t data, any_t item, const HeadPart hp, const char *path)
{
PicManager *pm = data;
MaybeAddStyleName(
((const NamedSprites *)item)->name, path,
&pm->headPartNames[hp]);
((const NamedSprites *)item)->name, path, &pm->headPartNames[hp]);
return MAP_OK;
}
static int MaybeAddHairSpriteName(any_t data, any_t item)
{
return MaybeAddHeadPartSpriteName(data, item, HEAD_PART_HAIR, "chars/hairs/");
return MaybeAddHeadPartSpriteName(
data, item, HEAD_PART_HAIR, "chars/hairs/");
}
static int MaybeAddFacehairSpriteName(any_t data, any_t item)
{
return MaybeAddHeadPartSpriteName(data, item, HEAD_PART_FACEHAIR, "chars/facehairs/");
return MaybeAddHeadPartSpriteName(
data, item, HEAD_PART_FACEHAIR, "chars/facehairs/");
}
static int MaybeAddHatSpriteName(any_t data, any_t item)
{
return MaybeAddHeadPartSpriteName(data, item, HEAD_PART_HAT, "chars/hats/");
return MaybeAddHeadPartSpriteName(
data, item, HEAD_PART_HAT, "chars/hats/");
}
static int MaybeAddGlassesSpriteName(any_t data, any_t item)
{
return MaybeAddHeadPartSpriteName(data, item, HEAD_PART_GLASSES, "chars/glasses/");
return MaybeAddHeadPartSpriteName(
data, item, HEAD_PART_GLASSES, "chars/glasses/");
}
static int MaybeAddExitPicName(any_t data, any_t item)
{
@@ -505,10 +519,10 @@ static int ReloadTexture(any_t data, any_t item);
static int ReloadSpriteTexture(any_t data, any_t item);
void PicManagerReloadTextures(PicManager *pm)
{
hashmap_iterate(pm->pics, ReloadTexture, pm);
hashmap_iterate(pm->customPics, ReloadTexture, pm);
hashmap_iterate(pm->sprites, ReloadSpriteTexture, pm);
hashmap_iterate(pm->customSprites, ReloadSpriteTexture, pm);
hashmap_iterate(pm->pics, ReloadTexture, NULL);
hashmap_iterate(pm->customPics, ReloadTexture, NULL);
hashmap_iterate(pm->sprites, ReloadSpriteTexture, NULL);
hashmap_iterate(pm->customSprites, ReloadSpriteTexture, NULL);
}
static int ReloadTexture(any_t data, any_t item)
{

View File

@@ -1,7 +1,7 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2014-2016, 2018-2020, 2022-2023 Cong Xu
Copyright (c) 2014-2016, 2018-2020, 2022-2023, 2025 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -62,6 +62,8 @@ void PlayerDataAddOrUpdate(const NPlayerData pd)
p->Char.speed = 1;
p->WeaponUsages = WeaponUsagesNew();
LOG(LM_MAIN, LL_INFO, "add default player UID(%u) local(%s)", pd.UID,
p->IsLocal ? "true" : "false");
}
@@ -74,12 +76,12 @@ void PlayerDataAddOrUpdate(const NPlayerData pd)
{
p->Char.Class = StrCharacterClass("Jones");
}
#define ADDHEADPART(_hp, _pdPart) \
CFREE(p->Char.HeadParts[_hp]); \
p->Char.HeadParts[_hp] = NULL; \
if (strlen(_pdPart) > 0) \
{ \
CSTRDUP(p->Char.HeadParts[_hp], _pdPart); \
#define ADDHEADPART(_hp, _pdPart) \
CFREE(p->Char.HeadParts[_hp]); \
p->Char.HeadParts[_hp] = NULL; \
if (strlen(_pdPart) > 0) \
{ \
CSTRDUP(p->Char.HeadParts[_hp], _pdPart); \
}
ADDHEADPART(HEAD_PART_HAIR, pd.Hair);
ADDHEADPART(HEAD_PART_FACEHAIR, pd.Facehair);
@@ -111,7 +113,8 @@ void PlayerDataAddOrUpdate(const NPlayerData pd)
// Ready players as well
p->Ready = true;
LOG(LM_MAIN, LL_INFO, "update player UID(%d) maxHealth(%d) excessHealth(%d) HP(%d)", p->UID,
LOG(LM_MAIN, LL_INFO,
"update player UID(%d) maxHealth(%d) excessHealth(%d) HP(%d)", p->UID,
p->Char.maxHealth, p->Char.excessHealth, p->HP);
}
@@ -147,6 +150,7 @@ void PlayerRemove(const int uid)
static void PlayerTerminate(PlayerData *p)
{
CFREE(p->Char.bot);
WeaponUsagesTerminate(p->WeaponUsages);
}
NPlayerData PlayerDataDefault(const int idx)
@@ -240,7 +244,7 @@ NPlayerData PlayerDataDefault(const int idx)
break;
}
}
pd.HP = CampaignGetHP(&gCampaign);
pd.MaxHealth = CampaignGetMaxHP(&gCampaign);
pd.ExcessHealth = CampaignGetExcessHP(&gCampaign);

View File

@@ -28,6 +28,7 @@
#pragma once
#include "character.h"
#include "weapon_usage.h"
#define MAX_GUNS 3
#define MELEE_SLOT 2
@@ -52,7 +53,7 @@ typedef struct
NPlayerStats Stats;
NPlayerStats Totals;
map_t WeaponUsages; // of name -> usage (TODO: shots, hits)
WeaponUsages WeaponUsages;
// Used for end-of-game score tallying
int hp;

116
src/cdogs/weapon_usage.c Normal file
View File

@@ -0,0 +1,116 @@
/*
Copyright (c) 2025 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 "weapon_usage.h"
WeaponUsages WeaponUsagesNew(void)
{
return hashmap_new();
}
void WeaponUsagesTerminate(WeaponUsages wu)
{
hashmap_free(wu);
}
void WeaponUsagesUpdate(
WeaponUsages wu, const WeaponClass *wc, const int dShot, const int dHit)
{
// TODO: accuracy for explosives?
if (!wc || !wc->IsRealGun || WeaponClassIsHighDPS(wc))
{
return;
}
NWeaponUsage *w = NULL;
if (hashmap_get(wu, wc->name, (any_t *)&w) == MAP_MISSING)
{
CCALLOC(w, sizeof *w);
strcpy(w->Weapon, wc->name);
hashmap_put(wu, wc->name, w);
}
w->Shots += dShot;
w->Hits += dHit;
}
typedef struct
{
float accuracy;
int weight;
} GetAccuracyData;
static int GetAccuracyItem(any_t data, any_t item);
float WeaponUsagesGetAccuracy(const WeaponUsages wu)
{
CArray accuracies;
CArrayInit(&accuracies, sizeof(GetAccuracyData));
hashmap_iterate(wu, GetAccuracyItem, &accuracies);
float cumSum = 0;
int totalWeight = 0;
CA_FOREACH(const GetAccuracyData, gData, accuracies)
cumSum += gData->accuracy * gData->weight;
totalWeight += gData->weight;
CA_FOREACH_END()
return totalWeight > 0 ? cumSum / totalWeight : 0;
}
static int GetAccuracyItem(any_t data, any_t item)
{
NWeaponUsage *w = item;
if (w->Shots > 0)
{
GetAccuracyData gData;
gData.accuracy = (float)w->Hits / w->Shots;
const WeaponClass *wc = StrWeaponClass(w->Weapon);
gData.weight = MAX(wc->Lock, 1);
CArray *accuracies = data;
CArrayPushBack(accuracies, &gData);
}
return MAP_OK;
}
typedef struct
{
const WeaponClass *wc;
int ticks;
} GetFavoriteData;
static int GetFavoriteItem(any_t data, any_t item);
const WeaponClass *WeaponUsagesGetFavorite(const WeaponUsages wu)
{
// Find the weapon that spent the most time shooting
GetFavoriteData gData = {NULL, 0};
hashmap_iterate(wu, GetFavoriteItem, &gData);
return gData.wc;
}
static int GetFavoriteItem(any_t data, any_t item)
{
GetFavoriteData *gData = data;
NWeaponUsage *w = item;
const WeaponClass *wc = StrWeaponClass(w->Weapon);
const int ticks = w->Shots * MAX(wc->Lock, 1);
if (ticks > gData->ticks)
{
gData->wc = wc;
gData->ticks = ticks;
}
return MAP_OK;
}

39
src/cdogs/weapon_usage.h Normal file
View File

@@ -0,0 +1,39 @@
/*
Copyright (c) 2025 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 "c_hashmap/hashmap.h"
#include "weapon_class.h"
typedef map_t WeaponUsages; // of name -> usage (TODO: shots, hits)
WeaponUsages WeaponUsagesNew(void);
void WeaponUsagesTerminate(WeaponUsages wu);
void WeaponUsagesUpdate(
WeaponUsages wu, const WeaponClass *wc, const int dShot, const int dHit);
// Get overall accuracy for all the weapons
float WeaponUsagesGetAccuracy(const WeaponUsages wu);
const WeaponClass *WeaponUsagesGetFavorite(const WeaponUsages wu);

View File

@@ -81,10 +81,6 @@ void emscriptenLoadFiles()
{
fclose(file_scores);
}
else
{
SaveHighScores();
}
// players.cnf
FILE *file_players = fopen(GetConfigFilePath(PLAYER_TEMPLATE_FILE), "r");

View File

@@ -78,13 +78,13 @@ typedef struct
NPlayerStats Stats;
int Missions;
int LastMission;
map_t WeaponUsages; // of name -> usage (TODO: shots, hits)
WeaponUsages WeaponUsages;
} HighScoreEntry;
// High score structure is:
// campaign name > entries, sorted by score
static map_t sHighScores; // TODO: merge with autosaves
static void LoadHighScores(void);
// TODO: merge with autosaves
static map_t LoadHighScores(void);
static void SaveHighScores(map_t highScores);
// Get player high score if it applies, or 0 otherwise
static int GetPlayerHighScore(const Campaign *co, const PlayerData *p)
@@ -134,14 +134,14 @@ static GameLoopResult HighScoresScreenUpdate(GameLoopData *data, LoopRunner *l)
if (!IsPVP(hData.co->Entry.Mode) &&
GetNumPlayers(PLAYER_ANY, false, true) > 0)
{
LoadHighScores();
map_t highScores = LoadHighScores();
CArray *scores = NULL;
if (hashmap_get(sHighScores, hData.co->Entry.Path, (any_t *)&scores) ==
if (hashmap_get(highScores, hData.co->Entry.Path, (any_t *)&scores) ==
MAP_MISSING)
{
CMALLOC(scores, sizeof *scores);
CArrayInit(scores, sizeof(HighScoreEntry));
hashmap_put(sHighScores, hData.co->Entry.Path, scores);
hashmap_put(highScores, hData.co->Entry.Path, scores);
}
bool allTime = false;
@@ -166,7 +166,7 @@ static GameLoopResult HighScoresScreenUpdate(GameLoopData *data, LoopRunner *l)
}
p->lastMission = hData.co->MissionIndex;
CA_FOREACH_END()
SaveHighScores();
SaveHighScores(highScores);
// Show high scores screen if high enough
if (allTime)
@@ -218,12 +218,14 @@ static void DisplayAt(int x, int y, const char *s, int hilite)
FontStrMask(s, svec2i(x, y), mask);
}
#define INDEX_OFFSET 5
#define SCORE_OFFSET 30
#define TIME_OFFSET 55
#define KILLS_OFFSET 75
#define FACE_OFFSET 85
#define NAME_OFFSET 95
#define INDEX_OFFSET 0
#define SCORE_OFFSET (INDEX_OFFSET + 25)
#define TIME_OFFSET (SCORE_OFFSET + 30)
#define KILLS_OFFSET (TIME_OFFSET + 20)
#define ACC_OFFSET (KILLS_OFFSET + 20)
#define FAV_WEP_OFFSET (ACC_OFFSET + 30)
#define FACE_OFFSET (FAV_WEP_OFFSET + 10)
#define NAME_OFFSET (FACE_OFFSET + 10)
static int DisplayEntry(
const int x, const int y, const int idx, const HighScoreEntry *e,
@@ -236,11 +238,24 @@ static int DisplayEntry(
sprintf(s, "%d", (int)e->Stats.Score);
DisplayAt(x + SCORE_OFFSET - FontStrW(s), y, s, hilite);
const int timeSeconds = e->Stats.TimeTicks / FPS_FRAMELIMIT;
sprintf(s, "%d:%02d", timeSeconds / 60, timeSeconds % 60);
const int centiseconds =
(e->Stats.TimeTicks - timeSeconds * FPS_FRAMELIMIT) * 100 /
FPS_FRAMELIMIT;
sprintf(
s, "%d:%02d.%02d", timeSeconds / 60, timeSeconds % 60, centiseconds);
DisplayAt(x + TIME_OFFSET - FontStrW(s), y, s, hilite);
sprintf(s, "%d", (int)e->Stats.Kills);
DisplayAt(x + KILLS_OFFSET - FontStrW(s), y, s, hilite);
// TODO: show favourite weapon
sprintf(s, "%d%%", (int)(WeaponUsagesGetAccuracy(e->WeaponUsages) * 100));
DisplayAt(x + ACC_OFFSET - FontStrW(s), y, s, hilite);
const WeaponClass *wc = WeaponUsagesGetFavorite(e->WeaponUsages);
if (wc && wc->Icon)
{
PicRender(
wc->Icon, gGraphicsDevice.gameWindow.renderer,
svec2i(x + FAV_WEP_OFFSET - 15, y), colorWhite, 0, svec2_one(),
SDL_FLIP_NONE, Rect2iZero());
}
DrawHead(
gGraphicsDevice.gameWindow.renderer, &e->Character, DIRECTION_DOWN,
svec2i(x + FACE_OFFSET, y + 4));
@@ -251,7 +266,7 @@ static int DisplayEntry(
static void DisplayPage(const CArray *entries)
{
int x = 80;
int x = 60;
int y = 5 + FontH();
FontStr("High Scores:", svec2i(5, 5));
@@ -266,6 +281,10 @@ static void DisplayPage(const CArray *entries)
DisplayAt(x + TIME_OFFSET - FontStrW(s), y, s, false);
s = "Kills";
DisplayAt(x + KILLS_OFFSET - FontStrW(s), y, s, false);
s = "Acc.";
DisplayAt(x + ACC_OFFSET - FontStrW(s), y, s, false);
s = "Fav.Gun";
DisplayAt(x + FAV_WEP_OFFSET - FontStrW(s), y, s, false);
s = "Name";
DisplayAt(x + NAME_OFFSET, y, s, false);
y += 5 + FontH();
@@ -345,12 +364,19 @@ static GameLoopResult HighScoreUpdate(GameLoopData *data, LoopRunner *l)
return UPDATE_RESULT_OK;
}
typedef struct
{
yajl_gen g;
map_t highScores;
} SaveHighScoresData;
static int SaveWeaponUsage(any_t data, any_t item);
static int SaveHighScoreEntries(any_t data, any_t key)
{
yajl_gen g = (yajl_gen)data;
SaveHighScoresData *sData = data;
yajl_gen g = sData->g;
CArray *entries;
const int error =
hashmap_get(sHighScores, (const char *)key, (any_t *)&entries);
hashmap_get(sData->highScores, (const char *)key, (any_t *)&entries);
if (error != MAP_OK)
{
CASSERT(false, "cannot find high score entry");
@@ -401,6 +427,11 @@ static int SaveHighScoreEntries(any_t data, any_t key)
g, (const unsigned char *)"WeaponUsages", strlen("WeaponUsages")));
YAJL_CHECK(yajl_gen_map_open(g));
// TODO: weapon usage
const int res = hashmap_iterate(entry->WeaponUsages, SaveWeaponUsage, g);
if (res != MAP_OK)
{
return res;
}
YAJL_CHECK(yajl_gen_map_close(g));
YAJL_CHECK(yajl_gen_map_close(g));
@@ -409,7 +440,31 @@ static int SaveHighScoreEntries(any_t data, any_t key)
#undef YAJL_CHECK
return MAP_OK;
}
void SaveHighScores(void)
static int SaveWeaponUsage(any_t data, any_t item)
{
yajl_gen g = data;
NWeaponUsage *wu = item;
#define YAJL_CHECK(func) \
{ \
yajl_gen_status _status = func; \
if (_status != yajl_gen_status_ok) \
{ \
LOG(LM_MAIN, LL_ERROR, \
"JSON generator error for high score entry %s: %d\n", \
(const char *)wu->Weapon, (int)_status); \
return MAP_MISSING; \
} \
}
YAJL_CHECK(yajl_gen_string(
g, (const unsigned char *)wu->Weapon, strlen(wu->Weapon)));
YAJL_CHECK(yajl_gen_map_open(g));
YAJL_CHECK(YAJLAddIntPair(g, "Shots", (int)wu->Shots));
YAJL_CHECK(YAJLAddIntPair(g, "Hits", (int)wu->Hits));
YAJL_CHECK(yajl_gen_map_close(g));
#undef YAJL_CHECK
return MAP_OK;
}
static void SaveHighScores(map_t highScores)
{
yajl_gen g = yajl_gen_alloc(NULL);
if (g == NULL)
@@ -427,8 +482,9 @@ void SaveHighScores(void)
}
YAJL_CHECK(yajl_gen_map_open(g));
SaveHighScoresData sData = {g, highScores};
if (hashmap_iterate_keys_sorted(
sHighScores, SaveHighScoreEntries, (any_t *)g) != MAP_OK)
highScores, SaveHighScoreEntries, (any_t *)&sData) != MAP_OK)
{
LOG(LM_MAIN, LL_ERROR, "Failed to generate high score entries\n");
goto bail;
@@ -449,9 +505,9 @@ bail:
}
}
static void LoadHighScores(void)
static map_t LoadHighScores(void)
{
sHighScores = hashmap_new();
map_t highScores = hashmap_new();
const char *path = GetConfigFilePath(SCORES_FILE);
yajl_val node = YAJLReadFile(path);
if (node == NULL)
@@ -482,6 +538,7 @@ static void LoadHighScores(void)
yajl_val entryNode = entriesNode->values[j];
HighScoreEntry entry;
memset(&entry, 0, sizeof entry);
entry.WeaponUsages = WeaponUsagesNew();
YAJLStr(&entry.Name, entryNode, "Name");
int value;
YAJLInt(&value, entryNode, "Time");
@@ -544,16 +601,31 @@ static void LoadHighScores(void)
const char *wuPath[] = {"WeaponUsages", NULL};
yajl_val wuNode = yajl_tree_get(entryNode, wuPath, yajl_t_object);
if (!wuNode)
if (!wuNode || !YAJL_IS_OBJECT(wuNode))
{
LOG(LM_MAIN, LL_ERROR,
"Unexpected format for high score entry %s %d\n", path, j);
continue;
}
// TODO: weapon usage
for (int j = 0; j < (int)wuNode->u.object.len; j++)
{
const char *weaponName = wuNode->u.object.keys[j];
yajl_val usageNode = wuNode->u.object.values[j];
NWeaponUsage *w = NULL;
CMALLOC(w, sizeof *w);
strcpy(w->Weapon, weaponName);
YAJLInt((int *)&w->Shots, usageNode, "Shots");
YAJLInt((int *)&w->Hits, usageNode, "Hits");
if (hashmap_put(entry.WeaponUsages, weaponName, w) != MAP_OK)
{
LOG(LM_MAIN, LL_ERROR, "failed to load high scores (%s)",
path);
continue;
}
}
CArrayPushBack(entries, &entry);
}
if (hashmap_put(sHighScores, entriesPath, (any_t *)entries) != MAP_OK)
if (hashmap_put(highScores, entriesPath, (any_t *)entries) != MAP_OK)
{
LOG(LM_MAIN, LL_ERROR, "failed to load high scores (%s)", path);
continue;
@@ -562,21 +634,27 @@ static void LoadHighScores(void)
bail:
yajl_tree_free(node);
return highScores;
}
HighScoresData HighScoresDataLoad(const Campaign *co, GraphicsDevice *g)
{
LoadHighScores();
map_t highScores = LoadHighScores();
HighScoresData hData;
memset(&hData, 0, sizeof hData);
hData.g = g;
CArray *scores = NULL;
if (hashmap_get(sHighScores, co->Entry.Path, (any_t *)&scores) !=
if (hashmap_get(highScores, co->Entry.Path, (any_t *)&scores) !=
MAP_MISSING)
{
// copy scores
CArrayInit(&hData.scores, sizeof(HighScoreEntry));
CArrayCopy(&hData.scores, scores);
CA_FOREACH(HighScoreEntry, entry, hData.scores)
const HighScoreEntry *src = CArrayGet(scores, _ca_index);
CSTRDUP(entry->Name, src->Name);
entry->WeaponUsages = hashmap_copy(src->WeaponUsages, NULL);
CA_FOREACH_END()
}
return hData;
}
@@ -585,7 +663,7 @@ void HighScoresDataTerminate(HighScoresData *hData)
{
CA_FOREACH(HighScoreEntry, entry, hData->scores)
CFREE(entry->Name);
// TODO: free weapon usages
WeaponUsagesTerminate(entry->WeaponUsages);
CA_FOREACH_END()
CArrayTerminate(&hData->scores);
}

View File

@@ -35,7 +35,6 @@
#define SCORES_FILE "scores.json"
GameLoopData *HighScoresScreen(Campaign *co, GraphicsDevice *g);
void SaveHighScores(void);
typedef struct
{

View File

@@ -3,6 +3,8 @@ NServerInfo.CampaignName max_size:20
NCampaignDef.Path max_size:4096
NWeaponUsage.Weapon max_size:128
NPlayerData.Name max_size:20
NPlayerData.CharacterClass max_size:128
NPlayerData.Hair max_size:128
@@ -12,11 +14,14 @@ NPlayerData.Glasses max_size:128
NPlayerData.Weapons max_size:128
NPlayerData.Weapons max_count:4
NPlayerData.Ammo max_count:128
NPlayerData.WeaponUsages max_count:128
NTileSet.ClassName max_size:128
NTileSet.DoorClassName max_size:128
NTileSet.DoorClass2Name max_size:128
NThingDamage.SourceWeaponClassName max_size:128
NConfig.Name max_size:128
NConfig.Value max_size:128
@@ -33,6 +38,7 @@ NActorMelee.BulletClass max_size:128
NAddPickup.PickupClass max_size:128
NAddBullet.BulletClass max_size:128
NAddBullet.Gun max_size:128
NExploreTiles.Runs max_count:16

View File

@@ -24,7 +24,10 @@ PB_BIND(NCharColors, NCharColors, AUTO)
PB_BIND(NPlayerStats, NPlayerStats, AUTO)
PB_BIND(NPlayerData, NPlayerData, 2)
PB_BIND(NWeaponUsage, NWeaponUsage, AUTO)
PB_BIND(NPlayerData, NPlayerData, 4)
PB_BIND(NPlayerRemove, NPlayerRemove, AUTO)
@@ -132,7 +135,7 @@ PB_BIND(NGunFire, NGunFire, AUTO)
PB_BIND(NGunState, NGunState, AUTO)
PB_BIND(NAddBullet, NAddBullet, AUTO)
PB_BIND(NAddBullet, NAddBullet, 2)
PB_BIND(NTrigger, NTrigger, AUTO)

File diff suppressed because one or more lines are too long

View File

@@ -48,6 +48,12 @@ message NPlayerStats {
uint32 TimeTicks = 5;
}
message NWeaponUsage {
string Weapon = 1;
uint32 Shots = 2;
uint32 Hits = 3;
}
message NPlayerData {
string Name = 1;
string CharacterClass = 2;
@@ -66,6 +72,7 @@ message NPlayerData {
repeated NAmmo Ammo = 15;
uint32 HP = 16;
uint32 ExcessHealth = 17;
repeated NWeaponUsage WeaponUsages = 18;
}
message NPlayerRemove {
@@ -95,6 +102,7 @@ message NThingDamage {
uint32 Flags = 7;
int32 Special = 8;
int32 SpecialTicks = 9;
string SourceWeaponClassName = 10;
}
message NMapObjectAdd {
@@ -306,6 +314,7 @@ message NAddBullet {
int32 Elevation = 6;
uint32 Flags = 7;
int32 ActorUID = 8;
string Gun = 9;
}
message NTrigger {