Change ammo to a campaign-level option #495

Remove ammo as a config option
Also rework some campaigns with ammo and skip weapon menu options
This commit is contained in:
Cong
2020-06-23 21:15:38 +10:00
parent e08242ba6c
commit da4158ded6
31 changed files with 1800 additions and 1293 deletions

View File

@@ -1,7 +1,10 @@
{
"Version": 4,
"Version": 15,
"Title": "de_dust",
"Author": "Cong",
"Description": "",
"Ammo": false,
"WeaponPersist": false,
"SkipWeaponMenu": true,
"Missions": 1
}

View File

@@ -1,3 +1,4 @@
{
"Version": 13,
"Characters": []
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,10 @@
{
"Version": 4,
"Version": 15,
"Title": "Doom",
"Author": "Cong",
"Description": "Stuck on Mars for insubordination, you are a marine who has just arrived at the dullest assignment in the system. They say your employer, UAC, are performing secret experiments to do with teleportation, but what could go wrong with that?",
"Ammo": true,
"WeaponPersist": false,
"SkipWeaponMenu": true,
"Missions": 1
}

View File

@@ -1,3 +1,4 @@
{
"Version": 13,
"Characters": []
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,10 @@
{
"Version": 4,
"Version": 15,
"Title": "Map01",
"Author": "Cong",
"Description": "",
"Ammo": false,
"WeaponPersist": false,
"SkipWeaponMenu": true,
"Missions": 1
}

View File

@@ -1,3 +1,4 @@
{
"Version": 13,
"Characters": []
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,10 @@
{
"Version": 4,
"Version": 15,
"Title": "Map 07: Dead Simple",
"Author": "Cong",
"Description": "A simple deathmatch arena",
"Ammo": false,
"WeaponPersist": false,
"SkipWeaponMenu": true,
"Missions": 1
}

View File

@@ -1,3 +1,4 @@
{
"Version": 13,
"Characters": []
}

View File

@@ -5,12 +5,8 @@
"Type": "Static",
"Width": 24,
"Height": 28,
"WallStyle": 4,
"FloorStyle": 5,
"RoomStyle": 2,
"ExitStyle": 0,
"KeyStyle": 0,
"DoorStyle": 0,
"ExitStyle": "hazard",
"KeyStyle": "office",
"Objectives": [],
"Enemies": [],
"SpecialChars": [],
@@ -18,105 +14,170 @@
"EnemyDensity": 0,
"Weapons": ["Marine Pistol"],
"Song": "",
"WallColor": 18,
"FloorColor": 14,
"RoomColor": 4,
"AltColor": 0,
"Tiles": "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,4,4,4,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,4,1,4,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,4,1,4,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,4,4,4,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
"TileClasses": {
"1": {
"Name": "wall",
"Type": "Wall",
"Style": "stone",
"Mask": "94802cff",
"MaskAlt": "840000ff",
"CanWalk": false,
"IsOpaque": true,
"Shootable": true,
"IsRoom": false
},
"4": {
"Name": "",
"Type": "",
"Style": "",
"Mask": "ffffffff",
"MaskAlt": "ffffffff",
"CanWalk": false,
"IsOpaque": false,
"Shootable": false,
"IsRoom": false
},
"3": {
"Name": "tile",
"Type": "Floor",
"Style": "dirt",
"Mask": "007000ff",
"MaskAlt": "840000ff",
"CanWalk": true,
"IsOpaque": false,
"Shootable": false,
"IsRoom": true
},
"0": {
"Name": "tile",
"Type": "Floor",
"Style": "smallsquare",
"Mask": "484848ff",
"MaskAlt": "840000ff",
"CanWalk": true,
"IsOpaque": false,
"Shootable": false,
"IsRoom": false
}
},
"Tiles": ["1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
"1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1",
"1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1",
"1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,4,4,4,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,4,1,4,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,4,1,4,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,4,4,4,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,0,0,0,3,3,3,3,3,0,0,0,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,1",
"1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1",
"1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1",
"1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1",
"1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"],
"Access": ["0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"],
"StaticItems": [{
"MapObject": "Rockets spawner",
"Positions": [[9,
24],
[15,
24],
[4,
24],
[4,
3],
[9,
3],
[15,
3],
[12,
18],
[12,
9],
[9,
13],
[15,
13],
[7,
20],
[15,
15],
[17,
7]]
"MapObject": "Rockets ammo spawner",
"Positions": [[9, 24],
[15, 24],
[4, 24],
[4, 3],
[9, 3],
[15, 3],
[12, 18],
[12, 9],
[9, 13],
[15, 13],
[7, 20],
[15, 15],
[17, 7]]
},
{
"MapObject": "Shells spawner",
"Positions": [[2,
17],
[2,
10],
[22,
10],
[22,
17],
[22,
15]]
"MapObject": "Shells ammo spawner",
"Positions": [[2, 17],
[2, 10],
[22, 10],
[22, 17],
[22, 15]]
},
{
"MapObject": "Bullets spawner",
"Positions": [[20,
3],
[20,
24]]
"MapObject": "Bullets ammo spawner",
"Positions": [[20, 3],
[20, 24]]
},
{
"MapObject": "Super shotgun spawner",
"Positions": [[22,
12]]
"Positions": [[22, 12]]
},
{
"MapObject": "health spawner",
"Positions": [[9,
18],
[7,
13]]
"Positions": [[9, 18],
[7, 13]]
},
{
"MapObject": "Chaingun spawner",
"Positions": [[15,
18]]
"Positions": [[15, 18]]
},
{
"MapObject": "Mini cells spawner",
"Positions": [[17,
19],
[7,
7]]
"MapObject": "Mini cells ammo spawner",
"Positions": [[17, 19],
[7, 7]]
},
{
"MapObject": "Plasma gun spawner",
"Positions": [[15,
9]]
"Positions": [[15, 9]]
},
{
"MapObject": "Launcher spawner",
"Positions": [[9,
9]]
"Positions": [[9, 9]]
}],
"StaticWrecks": [],
"StaticCharacters": [],
"StaticObjectives": [],
"StaticKeys": [],
"Start": [0,
0],
"Start": [0, 0],
"Exit": {
"Start": [0,
0],
"End": [0,
0]
"Start": [0, 0],
"End": [0, 0]
}
}]
}

View File

@@ -1,7 +1,10 @@
{
"Version": 12,
"Version": 15,
"Title": "Temple of Carnage",
"Author": "Sauer2",
"Description": "A temple for the deities of massacre!",
"Ammo": false,
"WeaponPersist": false,
"SkipWeaponMenu": true,
"Missions": 1
}

View File

@@ -1,4 +1,4 @@
{
"Version": 12,
"Version": 13,
"Characters": []
}

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@
"Title": "Doom",
"Author": "Cong",
"Description": "Stuck on Mars for insubordination, you are a marine who has just arrived at the dullest assignment in the system. They say your employer, UAC, are performing secret experiments to do with teleportation, but what could go wrong with that?",
"Ammo": true,
"WeaponPersist": true,
"SkipWeaponMenu": true,
"Missions": 8

View File

@@ -717,7 +717,7 @@ static void FireWeapon(TActor *a, Weapon *w)
}
if (!ActorCanFireWeapon(a, w))
{
if (!WeaponIsLocked(w) && ConfigGetBool(&gConfig, "Game.Ammo"))
if (!WeaponIsLocked(w) && gCampaign.Setting.Ammo)
{
CASSERT(
ActorWeaponGetAmmo(a, w->Gun) == 0, "should be out of ammo");
@@ -733,7 +733,7 @@ static void FireWeapon(TActor *a, Weapon *w)
ActorFire(w, a);
if (a->PlayerUID >= 0)
{
if (ConfigGetBool(&gConfig, "Game.Ammo") && w->Gun->AmmoId >= 0)
if (gCampaign.Setting.Ammo && w->Gun->AmmoId >= 0)
{
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_USE_AMMO);
e.u.UseAmmo.UID = a->uid;
@@ -1103,7 +1103,7 @@ static void ActorAddGunPickup(const TActor *actor);
static void ActorDie(TActor *actor)
{
// Add an ammo pickup of the actor's gun
if (ConfigGetBool(&gConfig, "Game.Ammo"))
if (gCampaign.Setting.Ammo)
{
ActorAddAmmoPickup(actor);
}
@@ -1432,8 +1432,7 @@ static void ActorAddAmmoPickup(const TActor *actor)
return false;
}
const bool hasAmmo = ActorWeaponGetAmmo(a, w->Gun) != 0;
return !WeaponIsLocked(w) &&
(!ConfigGetBool(&gConfig, "Game.Ammo") || hasAmmo);
return !WeaponIsLocked(w) && (!gCampaign.Setting.Ammo || hasAmmo);
}
bool ActorTrySwitchWeapon(const TActor *a, const bool allGuns)
{

View File

@@ -1,30 +1,30 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2015, 2017-2018 Cong Xu
All rights reserved.
Copyright (c) 2013-2015, 2017-2018, 2020 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 "ai_coop.h"
@@ -36,8 +36,7 @@
#define CONFUSION_STATE_TICKS_MIN 25
#define CONFUSION_STATE_TICKS_RANGE 25
#define DISTANCE_TO_KEEP_OUT_OF_WAY SQUARED(4*16)
#define DISTANCE_TO_KEEP_OUT_OF_WAY SQUARED(4 * 16)
static int AICoopGetCmdNormal(TActor *actor);
int AICoopGetCmd(TActor *actor, const int ticks)
@@ -64,9 +63,8 @@ int AICoopGetCmd(TActor *actor, const int ticks)
actor->aiContext->Delay = MAX(0, actor->aiContext->Delay - ticks);
if (actor->aiContext->Delay == 0)
{
actor->aiContext->Delay =
CONFUSION_STATE_TICKS_MIN +
(rand() % CONFUSION_STATE_TICKS_RANGE);
actor->aiContext->Delay = CONFUSION_STATE_TICKS_MIN +
(rand() % CONFUSION_STATE_TICKS_RANGE);
if (s->Type == AI_CONFUSION_CONFUSED)
{
s->Type = AI_CONFUSION_CORRECT;
@@ -76,9 +74,8 @@ int AICoopGetCmd(TActor *actor, const int ticks)
ActorSetAIState(actor, AI_STATE_CONFUSED);
s->Type = AI_CONFUSION_CONFUSED;
// Generate the confused action
s->Cmd = rand() &
(CMD_LEFT | CMD_RIGHT | CMD_UP | CMD_DOWN |
CMD_BUTTON1 | CMD_BUTTON2);
s->Cmd = rand() & (CMD_LEFT | CMD_RIGHT | CMD_UP | CMD_DOWN |
CMD_BUTTON1 | CMD_BUTTON2);
}
}
// Choose confusion action based on state
@@ -136,24 +133,25 @@ static int AICoopGetCmdNormal(TActor *actor)
struct vec2i v;
struct vec2 dangerBulletPos = svec2_zero();
for (v.x = actorTilePos.x - 1;
v.x <= actorTilePos.x + 1 && svec2_is_zero(dangerBulletPos);
v.x++)
v.x <= actorTilePos.x + 1 && svec2_is_zero(dangerBulletPos); v.x++)
{
for (v.y = actorTilePos.y - 1;
v.y <= actorTilePos.y + 1 && svec2_is_zero(dangerBulletPos);
v.y++)
v.y <= actorTilePos.y + 1 && svec2_is_zero(dangerBulletPos);
v.y++)
{
const Tile *t = MapGetTile(&gMap, v);
if (t == NULL) continue;
if (t == NULL)
continue;
CA_FOREACH(const ThingId, tid, t->things)
// Only look for bullets
if (tid->Kind != KIND_MOBILEOBJECT) continue;
const TMobileObject *mo = CArrayGet(&gMobObjs, tid->Id);
if (mo->bulletClass->HurtAlways)
{
dangerBulletPos = mo->thing.Pos;
break;
}
// Only look for bullets
if (tid->Kind != KIND_MOBILEOBJECT)
continue;
const TMobileObject *mo = CArrayGet(&gMobObjs, tid->Id);
if (mo->bulletClass->HurtAlways)
{
dangerBulletPos = mo->thing.Pos;
break;
}
CA_FOREACH_END()
}
}
@@ -165,7 +163,7 @@ static int AICoopGetCmdNormal(TActor *actor)
// Check the weapon for ammo
int lowAmmoGun = -1;
if (ConfigGetBool(&gConfig, "Game.Ammo") && actor->aiContext->OnGunId == -1)
if (gCampaign.Setting.Ammo && actor->aiContext->OnGunId == -1)
{
// Check all our weapons
// Prefer guns using ammo
@@ -213,12 +211,10 @@ static int AICoopGetCmdNormal(TActor *actor)
// - if we have a free slot
if (actor->aiContext->OnGunId != -1)
{
const WeaponClass *wc =
IdWeaponClass(actor->aiContext->OnGunId);
const WeaponClass *wc = IdWeaponClass(actor->aiContext->OnGunId);
if ((lowAmmoGun != -1 && !ActorHasGun(actor, wc)) ||
(wc->IsGrenade ?
ActorGetNumGrenades(actor) < MAX_GRENADES :
ActorGetNumGuns(actor) < MAX_GUNS))
(wc->IsGrenade ? ActorGetNumGrenades(actor) < MAX_GRENADES
: ActorGetNumGuns(actor) < MAX_GUNS))
{
actor->aiContext->OnGunId = -1;
// Pick it up
@@ -239,10 +235,10 @@ static int AICoopGetCmdNormal(TActor *actor)
for (int uid = 0; uid < actor->PlayerUID; uid++)
{
const PlayerData *pd = PlayerDataGetByUID(uid);
if (pd == NULL || !IsPlayerAlive(pd)) continue;
if (pd == NULL || !IsPlayerAlive(pd))
continue;
const TActor *p = ActorGetByUID(pd->ActorUID);
const float distance2 = svec2_distance_squared(
actor->Pos, p->Pos);
const float distance2 = svec2_distance_squared(actor->Pos, p->Pos);
if (!closestPlayer || distance2 < minDistance2)
{
minDistance2 = distance2;
@@ -265,7 +261,7 @@ static int AICoopGetCmdNormal(TActor *actor)
{
distanceTooFarFromPlayer = 2.0f;
}
if (closestPlayer && minDistance2 > SQUARED(distanceTooFarFromPlayer*16))
if (closestPlayer && minDistance2 > SQUARED(distanceTooFarFromPlayer * 16))
{
ActorSetAIState(actor, AI_STATE_FOLLOW);
return SmartGoto(actor, closestPlayer->Pos, minDistance2);
@@ -276,8 +272,8 @@ static int AICoopGetCmdNormal(TActor *actor)
if (closestEnemy)
{
const float minEnemyDistance = CHEBYSHEV_DISTANCE(
actor->Pos.x, actor->Pos.y,
closestEnemy->Pos.x, closestEnemy->Pos.y);
actor->Pos.x, actor->Pos.y, closestEnemy->Pos.x,
closestEnemy->Pos.y);
// Also only engage if there's a clear shot
if (minEnemyDistance > 0 && minEnemyDistance < 12 * 16 &&
AIHasClearShot(actor->Pos, closestEnemy->Pos))
@@ -301,7 +297,7 @@ static int AICoopGetCmdNormal(TActor *actor)
// Look for objectives nearby to complete
int cmd;
if (TryCompleteNearbyObjective(
actor, closestPlayer, distanceTooFarFromPlayer, &cmd))
actor, closestPlayer, distanceTooFarFromPlayer, &cmd))
{
return cmd;
}
@@ -310,15 +306,14 @@ static int AICoopGetCmdNormal(TActor *actor)
// run into them, but keep away if we're too close
if (closestPlayer)
{
if (minDistance2 > SQUARED(2*16))
if (minDistance2 > SQUARED(2 * 16))
{
ActorSetAIState(actor, AI_STATE_FOLLOW);
return SmartGoto(actor, closestPlayer->Pos, minDistance2);
}
else if (minDistance2 < SQUARED(4*16/3))
else if (minDistance2 < SQUARED(4 * 16 / 3))
{
return
CmdGetReverse(AIGotoDirect(actor->Pos, closestPlayer->Pos));
return CmdGetReverse(AIGotoDirect(actor->Pos, closestPlayer->Pos));
}
}
@@ -384,8 +379,7 @@ static int SmartGoto(
typedef struct
{
float Distance2;
union
{
union {
const Objective *Objective;
int UID;
} u;
@@ -422,20 +416,18 @@ static bool TryCompleteNearbyObjective(
hasNoUpdates =
objState->u.Obj && objState->LastDone == objState->u.Obj->done;
break;
case AI_OBJECTIVE_TYPE_KILL:
{
const TActor *target = ActorGetByUID(objState->u.UID);
hasNoUpdates = target->health > 0;
// Update target position
objState->Goal = target->thing.Pos;
}
break;
case AI_OBJECTIVE_TYPE_PICKUP:
{
const Pickup *p = PickupGetByUID(objState->u.UID);
hasNoUpdates = p != NULL && p->isInUse;
}
break;
case AI_OBJECTIVE_TYPE_KILL: {
const TActor *target = ActorGetByUID(objState->u.UID);
hasNoUpdates = target->health > 0;
// Update target position
objState->Goal = target->thing.Pos;
}
break;
case AI_OBJECTIVE_TYPE_PICKUP: {
const Pickup *p = PickupGetByUID(objState->u.UID);
hasNoUpdates = p != NULL && p->isInUse;
}
break;
default:
// Do nothing
break;
@@ -458,8 +450,9 @@ static bool TryCompleteNearbyObjective(
// First, check if mission complete;
// if so (and there's a path) go to exit
const struct vec2 exitPos = MapGetExitPos(&gMap);
if (CanCompleteMission(&gMission) && CanGetObjective(
exitPos, actor->Pos, closestPlayer, distanceTooFarFromPlayer))
if (CanCompleteMission(&gMission) &&
CanGetObjective(
exitPos, actor->Pos, closestPlayer, distanceTooFarFromPlayer))
{
ActorSetAIState(actor, AI_STATE_NEXT_OBJECTIVE);
objState->Type = AI_OBJECTIVE_TYPE_EXIT;
@@ -475,41 +468,41 @@ static bool TryCompleteNearbyObjective(
// Starting from the closest objectives, find one we can go to
CA_FOREACH(ClosestObjective, c, objectives)
if (CanGetObjective(
if (CanGetObjective(
c->Pos, actor->Pos, closestPlayer, distanceTooFarFromPlayer))
{
ActorSetAIState(actor, AI_STATE_NEXT_OBJECTIVE);
objState->Type = c->Type;
objState->IsDestructible = c->IsDestructible;
switch (c->Type)
{
ActorSetAIState(actor, AI_STATE_NEXT_OBJECTIVE);
objState->Type = c->Type;
objState->IsDestructible = c->IsDestructible;
switch (c->Type)
{
case AI_OBJECTIVE_TYPE_KEY:
objState->LastDone = KeycardCount(gMission.KeyFlags);
break;
case AI_OBJECTIVE_TYPE_NORMAL:
objState->u.Obj = c->u.Objective;
objState->LastDone = c->u.Objective->done;
break;
case AI_OBJECTIVE_TYPE_KILL:
objState->u.UID = c->u.UID;
break;
case AI_OBJECTIVE_TYPE_PICKUP:
objState->u.UID = c->u.UID;
break;
default:
// Do nothing
break;
}
objState->Goal = c->Pos;
*cmdOut = GotoObjective(actor, c->Distance2);
return true;
case AI_OBJECTIVE_TYPE_KEY:
objState->LastDone = KeycardCount(gMission.KeyFlags);
break;
case AI_OBJECTIVE_TYPE_NORMAL:
objState->u.Obj = c->u.Objective;
objState->LastDone = c->u.Objective->done;
break;
case AI_OBJECTIVE_TYPE_KILL:
objState->u.UID = c->u.UID;
break;
case AI_OBJECTIVE_TYPE_PICKUP:
objState->u.UID = c->u.UID;
break;
default:
// Do nothing
break;
}
objState->Goal = c->Pos;
*cmdOut = GotoObjective(actor, c->Distance2);
return true;
}
CA_FOREACH_END()
return false;
}
static bool OnClosestPickupGun(
ClosestObjective *co, const Pickup *p,
const TActor *actor, const TActor *closestPlayer);
ClosestObjective *co, const Pickup *p, const TActor *actor,
const TActor *closestPlayer);
static int CompareClosestObjective(const void *v1, const void *v2);
static void FindObjectivesSortedByDistance(
CArray *objectives, const TActor *actor, const TActor *closestPlayer)
@@ -519,8 +512,7 @@ static void FindObjectivesSortedByDistance(
// If PVP, find the closest enemy and go to them
if (IsPVP(gCampaign.Entry.Mode))
{
const TActor *closestEnemy =
AIGetClosestVisibleEnemy(actor, true);
const TActor *closestEnemy = AIGetClosestVisibleEnemy(actor, true);
if (closestEnemy != NULL)
{
ClosestObjective co;
@@ -536,170 +528,167 @@ static void FindObjectivesSortedByDistance(
// Look for pickups
CA_FOREACH(const Pickup, p, gPickups)
if (!p->isInUse)
if (!p->isInUse)
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = p->thing.Pos;
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
switch (p->class->Type)
{
case PICKUP_KEYCARD:
co.Type = AI_OBJECTIVE_TYPE_KEY;
break;
case PICKUP_JEWEL:
break;
case PICKUP_HEALTH:
// Pick up if we are on low health, and lower than lead player
if (actor->health > ModeMaxHealth(gCampaign.Entry.Mode) / 4)
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = p->thing.Pos;
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
switch (p->class->Type)
{
case PICKUP_KEYCARD:
co.Type = AI_OBJECTIVE_TYPE_KEY;
break;
case PICKUP_JEWEL:
break;
case PICKUP_HEALTH:
// Pick up if we are on low health, and lower than lead player
if (actor->health > ModeMaxHealth(gCampaign.Entry.Mode) / 4)
{
continue;
}
co.Type = AI_OBJECTIVE_TYPE_PICKUP;
co.u.UID = p->UID;
break;
case PICKUP_AMMO:
{
// Pick up if we use this ammo, have less ammo than starting,
// and lower than lead player, who uses the ammo
const int ammoId = p->class->u.Ammo.Id;
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int ammoAmount = *(int *)CArrayGet(&actor->ammo, ammoId);
if (!ActorUsesAmmo(actor, ammoId) ||
ammoAmount > ammo->Amount ||
(closestPlayer != NULL &&
ActorUsesAmmo(closestPlayer, ammoId) &&
ammoAmount > *(int *)CArrayGet(&closestPlayer->ammo, ammoId)))
{
continue;
}
co.Type = AI_OBJECTIVE_TYPE_PICKUP;
co.u.UID = p->UID;
}
break;
case PICKUP_GUN:
if (!OnClosestPickupGun(&co, p, actor, closestPlayer))
{
continue;
}
break;
default:
// Not something we want to pick up
continue;
}
// Check if the pickup is actually accessible
// This is because random spawning may cause some pickups to be spawned
// in inaccessible areas
if (!TileCanWalk(MapGetTile(&gMap, Vec2ToTile(co.Pos))))
co.Type = AI_OBJECTIVE_TYPE_PICKUP;
co.u.UID = p->UID;
break;
case PICKUP_AMMO: {
// Pick up if we use this ammo, have less ammo than starting,
// and lower than lead player, who uses the ammo
const int ammoId = p->class->u.Ammo.Id;
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int ammoAmount = *(int *)CArrayGet(&actor->ammo, ammoId);
if (!ActorUsesAmmo(actor, ammoId) || ammoAmount > ammo->Amount ||
(closestPlayer != NULL && ActorUsesAmmo(closestPlayer, ammoId) &&
ammoAmount > *(int *)CArrayGet(&closestPlayer->ammo, ammoId)))
{
continue;
}
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
if (co.Type == AI_OBJECTIVE_TYPE_NORMAL)
co.Type = AI_OBJECTIVE_TYPE_PICKUP;
co.u.UID = p->UID;
}
break;
case PICKUP_GUN:
if (!OnClosestPickupGun(&co, p, actor, closestPlayer))
{
const int objective = ObjectiveFromThing(p->thing.flags);
co.u.Objective =
CArrayGet(&gMission.missionData->Objectives, objective);
continue;
}
CArrayPushBack(objectives, &co);
break;
default:
// Not something we want to pick up
continue;
}
// Check if the pickup is actually accessible
// This is because random spawning may cause some pickups to be spawned
// in inaccessible areas
if (!TileCanWalk(MapGetTile(&gMap, Vec2ToTile(co.Pos))))
{
continue;
}
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
if (co.Type == AI_OBJECTIVE_TYPE_NORMAL)
{
const int objective = ObjectiveFromThing(p->thing.flags);
co.u.Objective =
CArrayGet(&gMission.missionData->Objectives, objective);
}
CArrayPushBack(objectives, &co);
CA_FOREACH_END()
// Look for destructibles
CA_FOREACH(const TObject, o, gObjs)
if (!o->isInUse)
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = o->thing.Pos;
co.IsDestructible = true;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
if (!(o->thing.flags & THING_OBJECTIVE))
{
continue;
}
// Destructible objective; go towards it and fire
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
if (co.Type == AI_OBJECTIVE_TYPE_NORMAL)
{
const int objective = ObjectiveFromThing(o->thing.flags);
co.u.Objective =
CArrayGet(&gMission.missionData->Objectives, objective);
}
CArrayPushBack(objectives, &co);
if (!o->isInUse)
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = o->thing.Pos;
co.IsDestructible = true;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
if (!(o->thing.flags & THING_OBJECTIVE))
{
continue;
}
// Destructible objective; go towards it and fire
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
if (co.Type == AI_OBJECTIVE_TYPE_NORMAL)
{
const int objective = ObjectiveFromThing(o->thing.flags);
co.u.Objective =
CArrayGet(&gMission.missionData->Objectives, objective);
}
CArrayPushBack(objectives, &co);
CA_FOREACH_END()
// Look for kill or rescue objectives
CA_FOREACH(const TActor, a, gActors)
if (!a->isInUse)
{
continue;
}
const Thing *ti = &a->thing;
if (!(ti->flags & THING_OBJECTIVE))
{
continue;
}
const int objective = ObjectiveFromThing(ti->flags);
const Objective *o =
CArrayGet(&gMission.missionData->Objectives, objective);
if (o->Type != OBJECTIVE_KILL && o->Type != OBJECTIVE_RESCUE)
{
continue;
}
// Only rescue those that need to be rescued
if (o->Type == OBJECTIVE_RESCUE && !(a->flags & FLAGS_PRISONER))
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = ti->Pos;
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
co.u.Objective = o;
CArrayPushBack(objectives, &co);
if (!a->isInUse)
{
continue;
}
const Thing *ti = &a->thing;
if (!(ti->flags & THING_OBJECTIVE))
{
continue;
}
const int objective = ObjectiveFromThing(ti->flags);
const Objective *o =
CArrayGet(&gMission.missionData->Objectives, objective);
if (o->Type != OBJECTIVE_KILL && o->Type != OBJECTIVE_RESCUE)
{
continue;
}
// Only rescue those that need to be rescued
if (o->Type == OBJECTIVE_RESCUE && !(a->flags & FLAGS_PRISONER))
{
continue;
}
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = ti->Pos;
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
co.u.Objective = o;
CArrayPushBack(objectives, &co);
CA_FOREACH_END()
// Look for explore objectives
CA_FOREACH(const Objective, o, gMission.missionData->Objectives)
if (o->Type != OBJECTIVE_INVESTIGATE)
{
continue;
}
if (ObjectiveIsComplete(o) || MapGetExploredPercentage(&gMap) == 100)
{
continue;
}
// Find the nearest unexplored tile
const struct vec2i actorTile = Vec2ToTile(actor->Pos);
const struct vec2i unexploredTile = MapSearchTileAround(
&gMap, actorTile, MapTileIsUnexplored);
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = Vec2CenterOfTile(unexploredTile);
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
co.u.Objective = o;
CArrayPushBack(objectives, &co);
if (o->Type != OBJECTIVE_INVESTIGATE)
{
continue;
}
if (ObjectiveIsComplete(o) || MapGetExploredPercentage(&gMap) == 100)
{
continue;
}
// Find the nearest unexplored tile
const struct vec2i actorTile = Vec2ToTile(actor->Pos);
const struct vec2i unexploredTile =
MapSearchTileAround(&gMap, actorTile, MapTileIsUnexplored);
ClosestObjective co;
memset(&co, 0, sizeof co);
co.Pos = Vec2CenterOfTile(unexploredTile);
co.IsDestructible = false;
co.Type = AI_OBJECTIVE_TYPE_NORMAL;
co.Distance2 = svec2_distance_squared(actor->Pos, co.Pos);
co.u.Objective = o;
CArrayPushBack(objectives, &co);
CA_FOREACH_END()
// Sort according to distance
qsort(
objectives->data,
objectives->size, objectives->elemSize, CompareClosestObjective);
objectives->data, objectives->size, objectives->elemSize,
CompareClosestObjective);
}
static bool OnClosestPickupGun(
ClosestObjective *co, const Pickup *p,
const TActor *actor, const TActor *closestPlayer)
ClosestObjective *co, const Pickup *p, const TActor *actor,
const TActor *closestPlayer)
{
if (!ConfigGetBool(&gConfig, "Game.Ammo"))
if (!gCampaign.Setting.Ammo)
{
return false;
}
@@ -727,9 +716,8 @@ static bool OnClosestPickupGun(
const Ammo *ammo = AmmoGetById(&gAmmo, ammoId);
const int ammoAmount = *(int *)CArrayGet(&actor->ammo, ammoId);
if (ammoAmount >= ammo->Amount ||
(closestPlayer != NULL &&
ActorUsesAmmo(closestPlayer, ammoId) &&
ammoAmount > *(int *)CArrayGet(&closestPlayer->ammo, ammoId)))
(closestPlayer != NULL && ActorUsesAmmo(closestPlayer, ammoId) &&
ammoAmount > *(int *)CArrayGet(&closestPlayer->ammo, ammoId)))
{
continue;
}
@@ -795,8 +783,7 @@ static bool IsPosCloseEnoughToPlayer(
{
return true;
}
const float distanceFromPlayer2 =
svec2_distance_squared(pos, player->Pos);
const float distanceFromPlayer2 = svec2_distance_squared(pos, player->Pos);
const float distanceMax2 = SQUARED(distanceTooFarFromPlayer * 16);
return distanceFromPlayer2 < distanceMax2;
}
@@ -822,7 +809,7 @@ static int GotoObjective(TActor *actor, const float objDistance2)
cmd = AIHunt(actor, goal);
if (AIHasClearShot(actor->Pos, goal))
{
cmd |= CMD_BUTTON1;
cmd |= CMD_BUTTON1;
}
}
return cmd;
@@ -854,7 +841,7 @@ void AICoopSelectWeapons(
gunCount++;
}
if (ConfigGetBool(&gConfig, "Game.Ammo"))
if (gCampaign.Setting.Ammo)
{
// Select pistol as an infinite-ammo backup
const WeaponClass *pistol = StrWeaponClass("Pistol");

View File

@@ -53,6 +53,7 @@ typedef struct
char *Title;
char *Author;
char *Description;
bool Ammo;
bool WeaponPersist;
bool SkipWeaponMenu;
CArray Missions; // of Mission

View File

@@ -579,7 +579,6 @@ Config ConfigDefault(void)
ConfigGroupAdd(&game,
ConfigNewInt("Lives", 2, 1, 5, 1, NULL, NULL));
ConfigGroupAdd(&game, ConfigNewBool("HealthPickups", true));
ConfigGroupAdd(&game, ConfigNewBool("Ammo", false));
ConfigGroupAdd(&game, ConfigNewBool("Fog", true));
ConfigGroupAdd(&game,
ConfigNewInt("SightRange", 15, 8, 40, 1, NULL, NULL));

View File

@@ -1,29 +1,29 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2017, 2019 Cong Xu
All rights reserved.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2017, 2019-2020 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 "player_hud.h"
@@ -31,8 +31,8 @@
#include "automap.h"
#include "draw/draw_actor.h"
#include "draw/nine_slice.h"
#include "hud_defs.h"
#include "hud/gauge.h"
#include "hud_defs.h"
#define SCORE_WIDTH 26
#define GRENADES_WIDTH 30
@@ -41,7 +41,6 @@
#define PLAYER_ICON_WIDTH 20
#define BAR_HEIGHT 13
void HUDPlayerInit(HUDPlayer *h)
{
memset(h, 0, sizeof *h);
@@ -51,15 +50,16 @@ void HUDPlayerInit(HUDPlayer *h)
void HUDPlayerUpdate(HUDPlayer *h, const PlayerData *p, const int ms)
{
const TActor *a = ActorGetByUID(p->ActorUID);
if (a == NULL) return;
if (a == NULL)
return;
// Health
HealthGaugeUpdate(&h->healthGauge, a, ms);
}
static void DrawPlayerStatus(
HUD *hud, const PlayerData *data, TActor *p,
const int flags, const HUDPlayer *h, const Rect2i r);
HUD *hud, const PlayerData *data, TActor *p, const int flags,
const HUDPlayer *h, const Rect2i r);
static void DrawPlayerObjectiveCompass(
const HUD *hud, TActor *a, const int hudPlayerIndex, const int numViews);
void DrawPlayerHUD(
@@ -67,7 +67,8 @@ void DrawPlayerHUD(
const int hudPlayerIndex, const Rect2i r, const int numViews)
{
TActor *a = IsPlayerAlive(p) ? ActorGetByUID(p->ActorUID) : NULL;
DrawPlayerStatus(hud, p, a, drawFlags, &hud->hudPlayers[hudPlayerIndex], r);
DrawPlayerStatus(
hud, p, a, drawFlags, &hud->hudPlayers[hudPlayerIndex], r);
HUDNumPopupsDrawPlayer(&hud->numPopups, hudPlayerIndex, drawFlags, r);
DrawPlayerObjectiveCompass(hud, a, hudPlayerIndex, numViews);
}
@@ -89,15 +90,15 @@ static void DrawGunIcons(
static void DrawGrenadeStatus(
GraphicsDevice *g, const TActor *a, const int flags, const Rect2i r);
static void DrawRadar(
GraphicsDevice *device, const TActor *p,
const int flags, const bool showExit);
GraphicsDevice *device, const TActor *p, const int flags,
const bool showExit);
static void DrawHealth(
GraphicsDevice *g, const TActor *a,
const int flags, const HUDPlayer *h, const Rect2i r, const color_t mask);
GraphicsDevice *g, const TActor *a, const int flags, const HUDPlayer *h,
const Rect2i r, const color_t mask);
// Draw player's score, health etc.
static void DrawPlayerStatus(
HUD *hud, const PlayerData *data, TActor *p,
const int flags, const HUDPlayer *h, const Rect2i r)
HUD *hud, const PlayerData *data, TActor *p, const int flags,
const HUDPlayer *h, const Rect2i r)
{
const color_t mask = data->Char.Colors.Body;
SDL_RendererFlip flip = SDL_FLIP_NONE;
@@ -192,15 +193,15 @@ static void DrawPlayerIcon(
}
if (flags & HUDFLAGS_PLACE_BOTTOM)
{
pos.y = g->cachedConfig.Res.y - pos.y + 2;
pos.y = g->cachedConfig.Res.y - pos.y + 2;
}
DrawActorPics(
&pics, svec2i_add(pos, offset),
Rect2iNew(svec2i(-12, -12), svec2i(12, 5)));
}
PicRender(
framePic, g->gameWindow.renderer, picPos, mask, 0,
svec2_one(), flip, Rect2iZero());
framePic, g->gameWindow.renderer, picPos, mask, 0, svec2_one(), flip,
Rect2iZero());
}
static void DrawScore(
GraphicsDevice *g, const PicManager *pm, const TActor *a, const int score,
@@ -227,7 +228,7 @@ static void DrawScore(
char s[50];
if (IsScoreNeeded(gCampaign.Entry.Mode))
{
if (ConfigGetBool(&gConfig, "Game.Ammo"))
if (gCampaign.Setting.Ammo)
{
// Display money instead of ammo
sprintf(s, "$%d", score);
@@ -293,8 +294,8 @@ static void DrawWeaponStatus(
}
Draw9Slice(
g, backPic, Rect2iNew(pos, backPicSize), 0, 2, 0, 4, false,
mask, SDL_FLIP_NONE);
g, backPic, Rect2iNew(pos, backPicSize), 0, 2, 0, 4, false, mask,
SDL_FLIP_NONE);
if (actor == NULL)
{
@@ -303,9 +304,10 @@ static void DrawWeaponStatus(
const Weapon *weapon = ACTOR_GET_WEAPON(actor);
const WeaponClass *wc = weapon->Gun;
CASSERT(wc != NULL, "weapon has no class");
// Draw gauge and ammo counter if ammo used
if (ConfigGetBool(&gConfig, "Game.Ammo") && wc->AmmoId >= 0)
if (gCampaign.Setting.Ammo && wc->AmmoId >= 0)
{
const Ammo *ammo = AmmoGetById(&gAmmo, wc->AmmoId);
const int amount = ActorWeaponGetAmmo(actor, wc);
@@ -318,10 +320,11 @@ static void DrawWeaponStatus(
{
const Pic *fillPic = PicManagerGetPic(pm, "hud/gauge_small_inner");
const struct vec2i fillPicSize = svec2i(
MAX(1, (AMMO_WIDTH - 1) * amount / ammo->Max), fillPic->size.y);
MAX(1, (AMMO_WIDTH - 1) * amount / ammo->Max),
fillPic->size.y);
Draw9Slice(
g, fillPic, Rect2iNew(svec2i(pos.x, pos.y), fillPicSize),
1, 1, 1, 1, false, colorBlue, SDL_FLIP_NONE);
g, fillPic, Rect2iNew(svec2i(pos.x, pos.y), fillPicSize), 1, 1,
1, 1, false, colorBlue, SDL_FLIP_NONE);
}
// Include ammo counter
@@ -376,6 +379,7 @@ static void DrawGunIcons(
const Weapon *weapon = ACTOR_GET_WEAPON(actor);
const WeaponClass *wc = weapon->Gun;
CASSERT(wc != NULL, "weapon has no class");
// Aligned right
const int right =
@@ -391,7 +395,7 @@ static void DrawGunIcons(
}
// Ammo icon
if (ConfigGetBool(&gConfig, "Game.Ammo") && wc->AmmoId >= 0)
if (gCampaign.Setting.Ammo && wc->AmmoId >= 0)
{
const Ammo *ammo = AmmoGetById(&gAmmo, wc->AmmoId);
const struct vec2i ammoPos = svec2i_add(pos, svec2i(6, 5));
@@ -405,8 +409,8 @@ static void DrawGunIcons(
}
static void DrawGrenadeIcons(
GraphicsDevice *g, const Pic *icon, const struct vec2i pos, const int width,
const int amount);
GraphicsDevice *g, const Pic *icon, const struct vec2i pos,
const int width, const int amount);
static void DrawGrenadeStatus(
GraphicsDevice *g, const TActor *a, const int flags, const Rect2i r)
{
@@ -436,8 +440,7 @@ static void DrawGrenadeStatus(
// Draw number of grenade icons; if there are too many draw one with the
// amount as text
const bool useAmmo =
ConfigGetBool(&gConfig, "Game.Ammo") && wc->AmmoId >= 0;
const bool useAmmo = gCampaign.Setting.Ammo && wc->AmmoId >= 0;
const int amount = useAmmo ? ActorWeaponGetAmmo(a, wc) : -1;
const Pic *icon = WeaponClassGetIcon(wc);
if (useAmmo && amount > 0 && amount <= MAX_GRENADE_ICONS)
@@ -462,8 +465,8 @@ static void DrawGrenadeStatus(
}
}
static void DrawGrenadeIcons(
GraphicsDevice *g, const Pic *icon, const struct vec2i pos, const int width,
const int amount)
GraphicsDevice *g, const Pic *icon, const struct vec2i pos,
const int width, const int amount)
{
const int dx = width / MAX_GRENADE_ICONS;
for (int i = 0; i < amount; i++)
@@ -476,8 +479,8 @@ static void DrawGrenadeIcons(
}
static void DrawRadar(
GraphicsDevice *device, const TActor *p,
const int flags, const bool showExit)
GraphicsDevice *device, const TActor *p, const int flags,
const bool showExit)
{
struct vec2i pos = svec2i_zero();
int w = device->cachedConfig.Res.x;
@@ -568,19 +571,15 @@ static void DrawRadar(
{
const struct vec2i playerPos = Vec2ToTile(p->thing.Pos);
AutomapDrawRegion(
device->gameWindow.renderer,
&gMap,
pos,
svec2i(AUTOMAP_SIZE, AUTOMAP_SIZE),
playerPos,
AUTOMAP_FLAGS_MASK,
device->gameWindow.renderer, &gMap, pos,
svec2i(AUTOMAP_SIZE, AUTOMAP_SIZE), playerPos, AUTOMAP_FLAGS_MASK,
showExit);
}
}
static void DrawHealth(
GraphicsDevice *g, const TActor *a,
const int flags, const HUDPlayer *h, const Rect2i r, const color_t mask)
GraphicsDevice *g, const TActor *a, const int flags, const HUDPlayer *h,
const Rect2i r, const color_t mask)
{
const int right =
SCORE_WIDTH + GRENADES_WIDTH + AMMO_WIDTH + GUN_ICON_WIDTH;
@@ -643,7 +642,7 @@ static void DrawPlayerObjectiveCompass(
}
static void DrawCompassArrow(
GraphicsDevice *g,const Rect2i r, const struct vec2 pos,
GraphicsDevice *g, const Rect2i r, const struct vec2 pos,
const struct vec2 playerPos, const color_t mask, const char *label);
static void DrawObjectiveCompass(
GraphicsDevice *g, const struct vec2 playerPos, const Rect2i r,
@@ -665,23 +664,23 @@ static void DrawObjectiveCompass(
{
Tile *tile = MapGetTile(map, tilePos);
CA_FOREACH(ThingId, tid, tile->things)
Thing *ti = ThingIdGetThing(tid);
if (!(ti->flags & THING_OBJECTIVE))
{
continue;
}
const int objective = ObjectiveFromThing(ti->flags);
const Objective *o =
CArrayGet(&gMission.missionData->Objectives, objective);
if (o->Flags & OBJECTIVE_HIDDEN)
{
continue;
}
if (!(o->Flags & OBJECTIVE_POSKNOWN) && !tile->isVisited)
{
continue;
}
DrawCompassArrow(g, r, ti->Pos, playerPos, o->color, NULL);
Thing *ti = ThingIdGetThing(tid);
if (!(ti->flags & THING_OBJECTIVE))
{
continue;
}
const int objective = ObjectiveFromThing(ti->flags);
const Objective *o =
CArrayGet(&gMission.missionData->Objectives, objective);
if (o->Flags & OBJECTIVE_HIDDEN)
{
continue;
}
if (!(o->Flags & OBJECTIVE_POSKNOWN) && !tile->isVisited)
{
continue;
}
DrawCompassArrow(g, r, ti->Pos, playerPos, o->color, NULL);
CA_FOREACH_END()
}
}
@@ -689,7 +688,7 @@ static void DrawObjectiveCompass(
#define COMP_SATURATE_DIST 350
static void DrawCompassArrow(
GraphicsDevice *g,const Rect2i r, const struct vec2 pos,
GraphicsDevice *g, const Rect2i r, const struct vec2 pos,
const struct vec2 playerPos, const color_t mask, const char *label)
{
const struct vec2 compassV = svec2_subtract(
@@ -705,9 +704,10 @@ static void DrawCompassArrow(
int xDist = (int)fabsf(compassV.x) - r.Size.x / 2;
int yDist = (int)fabsf(compassV.y) - r.Size.y / 2;
int lDist;
xDist > yDist ? lDist = xDist: (lDist = yDist);
HSV hsv = { -1.0, 1.0,
2.0 - 1.5 * MIN(lDist, COMP_SATURATE_DIST) / COMP_SATURATE_DIST };
xDist > yDist ? lDist = xDist : (lDist = yDist);
HSV hsv = {
-1.0, 1.0,
2.0 - 1.5 * MIN(lDist, COMP_SATURATE_DIST) / COMP_SATURATE_DIST};
const color_t tintedMask = ColorTint(mask, hsv);
struct vec2i textPos = svec2i_zero();
struct vec2i drawPos = svec2i_zero();
@@ -725,10 +725,10 @@ static void DrawCompassArrow(
if (compassV.x > 0)
{
// right edge
textPos = svec2i(
r.Pos.x + r.Size.x, r.Pos.y + r.Size.y / 2 + yInt);
drawPos = svec2i(
textPos.x - p->size.x, textPos.y - p->size.y / 2);
textPos =
svec2i(r.Pos.x + r.Size.x, r.Pos.y + r.Size.y / 2 + yInt);
drawPos =
svec2i(textPos.x - p->size.x, textPos.y - p->size.y / 2);
}
else
{
@@ -749,10 +749,10 @@ static void DrawCompassArrow(
if (compassV.y > 0)
{
// bottom edge
textPos = svec2i(
r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y + r.Size.y);
drawPos = svec2i(
textPos.x - p->size.x / 2, textPos.y - p->size.y);
textPos =
svec2i(r.Pos.x + r.Size.x / 2 + xInt, r.Pos.y + r.Size.y);
drawPos =
svec2i(textPos.x - p->size.x / 2, textPos.y - p->size.y);
}
else
{
@@ -777,9 +777,11 @@ static void DrawCompassArrow(
// Make sure the text is inside the screen
int padding = 8;
textPos.x = MAX(textPos.x, r.Pos.x + padding);
textPos.x = MIN(textPos.x, r.Pos.x + r.Size.x - textSize.x - padding);
textPos.x =
MIN(textPos.x, r.Pos.x + r.Size.x - textSize.x - padding);
textPos.y = MAX(textPos.y, r.Pos.y + padding);
textPos.y = MIN(textPos.y, r.Pos.y + r.Size.y - textSize.y - padding);
textPos.y =
MIN(textPos.y, r.Pos.y + r.Size.y - textSize.y - padding);
FontStrMask(label, textPos, tintedMask);
}
}

View File

@@ -281,6 +281,7 @@ int MapArchiveSave(const char *filename, CampaignSetting *c)
AddStringPair(root, "Title", c->Title);
AddStringPair(root, "Author", c->Author);
AddStringPair(root, "Description", c->Description);
AddBoolPair(root, "Ammo", c->Ammo);
AddBoolPair(root, "WeaponPersist", c->WeaponPersist);
AddBoolPair(root, "SkipWeaponMenu", c->SkipWeaponMenu);
AddIntPair(root, "Missions", (int)c->Missions.size);

View File

@@ -256,7 +256,7 @@ bool MapTryPlaceOneObject(
const int extraFlags, const bool isStrictMode)
{
// Don't place ammo spawners if ammo is disabled
if (!ConfigGetBool(&gConfig, "Game.Ammo") &&
if (!gCampaign.Setting.Ammo &&
mo->Type == MAP_OBJECT_TYPE_PICKUP_SPAWNER &&
mo->u.PickupClass->Type == PICKUP_AMMO)
{

View File

@@ -176,6 +176,7 @@ void MapNewLoadCampaignJSON(json_t *root, CampaignSetting *c)
c->Author = GetString(root, "Author");
CFREE(c->Description);
c->Description = GetString(root, "Description");
LoadBool(&c->Ammo, root, "Ammo");
LoadBool(&c->WeaponPersist, root, "WeaponPersist");
LoadBool(&c->SkipWeaponMenu, root, "SkipWeaponMenu");
}

View File

@@ -420,7 +420,6 @@ void NetServerSendGameStartMessages(NetServer *n, const int peerId)
// Send all game-specific config values
SendConfig(&gConfig, "Game.FriendlyFire", n, peerId);
SendConfig(&gConfig, "Game.FPS", n, peerId);
SendConfig(&gConfig, "Game.Ammo", n, peerId);
SendConfig(&gConfig, "Game.Fog", n, peerId);
SendConfig(&gConfig, "Game.SightRange", n, peerId);
SendConfig(&gConfig, "Game.AllyCollision", n, peerId);

View File

@@ -151,7 +151,7 @@ static void AddPickupAtObject(const TObject *o, const PickupType type)
strcpy(e.u.AddPickup.PickupClass, "health");
break;
case PICKUP_AMMO:
if (!ConfigGetBool(&gConfig, "Game.Ammo"))
if (!gCampaign.Setting.Ammo)
{
return;
}

View File

@@ -257,7 +257,7 @@ static bool HasGunUsingAmmo(const TActor *a, const int ammoId)
static bool TryPickupAmmo(TActor *a, const Pickup *p, const char **sound)
{
// Don't pickup if not using ammo
if (!ConfigGetBool(&gConfig, "Game.Ammo"))
if (!gCampaign.Setting.Ammo)
{
return false;
}

View File

@@ -178,7 +178,7 @@ void AmmoSpawnerInit(PowerupSpawner *p, Map *map, const int ammoId)
{
PowerupSpawnerInit(p, map);
// TODO: disable ammo spawners unless classic mode
p->Enabled = ConfigGetBool(&gConfig, "Game.Ammo") && !gCampaign.IsClient;
p->Enabled = gCampaign.Setting.Ammo && !gCampaign.IsClient;
p->SpawnTime = AMMO_SPAWN_TIME;
p->RateScaleFunc = AmmoScale;
p->PlaceFunc = AmmoPlace;

View File

@@ -1247,8 +1247,6 @@ int main(int argc, char *argv[])
ConfigGet(&gConfig, "Graphics.WindowWidth")->u.Int.Value = 800;
ConfigGet(&gConfig, "Graphics.WindowHeight")->u.Int.Value = 600;
ConfigGet(&gConfig, "Graphics.SecondWindow")->u.Bool.Value = false;
// Force enable ammo so that ammo spawners show up
ConfigGet(&gConfig, "Game.Ammo")->u.Bool.Value = true;
ConfigSetChanged(&gConfig);
GraphicsInit(ec.g, &gConfig);
ec.g->cachedConfig.IsEditor = true;

View File

@@ -102,6 +102,14 @@ static bool Draw(SDL_Window *win, struct nk_context *ctx, void *data)
// TODO: text area? Nuklear doesn't support it yet
DrawTextbox(ctx, cData->Description, 1024, "Description", NK_EDIT_BOX);
nk_layout_row_dynamic(ctx, ROW_HEIGHT, 1);
if (DrawCheckbox(
ctx, "Ammo",
"Enable ammo; if disabled all weapons have infinite ammo and "
"use up score instead",
&cData->c->Setting.Ammo))
{
changed = true;
}
if (DrawCheckbox(
ctx, "Persist weapons & ammo",
"Whether weapons picked up and extra ammo are "

View File

@@ -557,7 +557,6 @@ GameLoopData *GameOptions(const GameMode gm)
I("Game.PlayerHP");
I("Game.Lives");
I("Game.HealthPickups");
I("Game.Ammo");
I("Game.RandomSeed");
MenuAddSubmenu(ms->root, MenuCreateSeparator(""));
I("StartServer");
@@ -566,7 +565,6 @@ GameLoopData *GameOptions(const GameMode gm)
I("Dogfight.PlayerHP");
I("Dogfight.FirstTo");
I("Game.HealthPickups");
I("Game.Ammo");
I("Game.RandomSeed");
MenuAddSubmenu(ms->root,
MenuCreateAllowedWeapons("Weapons...", data));
@@ -577,7 +575,6 @@ GameLoopData *GameOptions(const GameMode gm)
I("Game.PlayerHP");
I("Deathmatch.Lives");
I("Game.HealthPickups");
I("Game.Ammo");
I("Game.RandomSeed");
MenuAddSubmenu(ms->root,
MenuCreateAllowedWeapons("Weapons...", data));