mirror of
https://github.com/cxong/cdogs-sdl.git
synced 2025-07-23 07:23:01 +02:00
Track bullet source weapon and calculate fav weapon, acc in high scores #151
This commit is contained in:
@@ -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)
|
||||
|
@@ -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();
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -38,7 +38,7 @@
|
||||
#include "map.h"
|
||||
#include "player.h"
|
||||
|
||||
#define NET_PROTOCOL_VERSION 15
|
||||
#define NET_PROTOCOL_VERSION 16
|
||||
|
||||
// Messages
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -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
116
src/cdogs/weapon_usage.c
Normal 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
39
src/cdogs/weapon_usage.h
Normal 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);
|
@@ -81,10 +81,6 @@ void emscriptenLoadFiles()
|
||||
{
|
||||
fclose(file_scores);
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveHighScores();
|
||||
}
|
||||
|
||||
// players.cnf
|
||||
FILE *file_players = fopen(GetConfigFilePath(PLAYER_TEMPLATE_FILE), "r");
|
||||
|
136
src/hiscores.c
136
src/hiscores.c
@@ -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);
|
||||
}
|
||||
|
@@ -35,7 +35,6 @@
|
||||
#define SCORES_FILE "scores.json"
|
||||
|
||||
GameLoopData *HighScoresScreen(Campaign *co, GraphicsDevice *g);
|
||||
void SaveHighScores(void);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
@@ -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 {
|
||||
|
Reference in New Issue
Block a user