From dac3d3127fc94231bdde0c0822bb12de01e9e836 Mon Sep 17 00:00:00 2001 From: enneract Date: Tue, 25 Feb 2014 13:03:43 +0100 Subject: 0.1.7 --- src/game/bg_misc.c | 244 ++++- src/game/bg_public.h | 41 +- src/game/g_active.c | 123 +-- src/game/g_buildable.c | 2399 +++++++++++++++++++++--------------------------- src/game/g_client.c | 8 +- src/game/g_cmds.c | 108 ++- src/game/g_combat.c | 26 +- src/game/g_local.h | 64 +- src/game/g_main.c | 141 +-- src/game/g_weapon.c | 4 +- src/game/tremulous.h | 89 +- 11 files changed, 1578 insertions(+), 1669 deletions(-) (limited to 'src/game') diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index a416ecb..ca443f2 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -69,7 +69,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; ASPAWN_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_OVERMIND, //int buildNum; @@ -105,7 +106,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qtrue, //qboolean uniqueTest; OVERMIND_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_BARRICADE, //int buildNum; @@ -141,7 +143,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; BARRICADE_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_ACIDTUBE, //int buildNum; @@ -177,7 +180,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; ACIDTUBE_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_TRAPPER, //int buildNum; @@ -213,7 +217,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; TRAPPER_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_BOOSTER, //int buildNum; @@ -250,7 +255,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; BOOSTER_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_HIVE, //int buildNum; @@ -285,7 +291,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; HIVE_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_H_SPAWN, //int buildNum; @@ -321,7 +328,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; HSPAWN_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_H_MGTURRET, //int buildNum; @@ -357,7 +365,12 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; MGTURRET_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + MGTURRET_R_IDLE, //float resistance; + MGTURRET_R_ACTIVE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_TESLAGEN, //int buildNum; @@ -393,7 +406,12 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; TESLAGEN_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + TESLAGEN_R_IDLE, //float resistance; + TESLAGEN_R_ACTIVE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_ARMOURY, //int buildNum; @@ -429,7 +447,12 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; ARMOURY_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + ARMOURY_RESISTANCE, //float resistance; + ARMOURY_RESISTANCE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_DCC, //int buildNum; @@ -465,7 +488,12 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; DC_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + DC_R_IDLE, //float resistance; + DC_R_ACTIVE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_MEDISTAT, //int buildNum; @@ -502,20 +530,24 @@ static const buildableAttributes_t bg_buildableList[ ] = qtrue, //qboolean transparentTest; qfalse, //qboolean uniqueTest; MEDISTAT_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + MEDISTAT_R_IDLE, //float resistance; + MEDISTAT_R_ACTIVE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_REACTOR, //int buildNum; "reactor", //char *buildName; "Reactor", //char *humanName; - "All structures except the telenode rely on a reactor to operate. " - "The reactor provides power for all the human structures either " - "directly or via repeaters. Only one reactor can be built at a time.", - "team_human_reactor", //char *entityName; + "A large nuclear generator, able to power substantial amounts of " + "structures.", + "team_human_reactor_big", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; REACTOR_BP, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S3 ), //int stages REACTOR_HEALTH, //int health; 0, //int regenRate; REACTOR_SPLASHDAMAGE, //int splashDamage; @@ -536,9 +568,14 @@ static const buildableAttributes_t bg_buildableList[ ] = 0, //int creepSize; qfalse, //qboolean dccTest; qfalse, //qboolean transparentTest; - qtrue, //qboolean uniqueTest; + qfalse, //qboolean uniqueTest; REACTOR_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + qtrue, //qboolean isPowerSource; + qfalse, //qboolean requiresPower; + REACTOR_RESISTANCE, //float resistance; + REACTOR_RESISTANCE, //float surgeResistance; + qfalse //qboolean hasStorage; }, { BA_H_REPEATER, //int buildNum; @@ -574,7 +611,135 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; REPEATER_VALUE, //int value; - qfalse //qboolean cuboid; + qfalse, //qboolean cuboid; + DEFAULT_POWER_SETTINGS + }, + { + BA_H_CAPBANK, //int buildNum; + "capbank", //char *buildName; + "Capacitor Bank", //char *humanName; + "A bank of capacitors able to compensate for " + "fluctations in the power network or even power " + "the network in the absence of a Reactor.", + "team_human_capbank", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + CAPBANK_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages + CAPBANK_HEALTH, //int health; + 0, //int regenRate; + CAPBANK_SPLASHDAMAGE, //int splashDamage; + CAPBANK_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + CAPBANK_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + CAPBANK_VALUE, //int value; + qfalse, //qboolean cuboid; + qtrue, //qboolean isPowerSource; + qfalse, //qboolean requiresPower; + CAPBANK_RESISTANCE, //float resistance; + CAPBANK_RESISTANCE, //float surgeResistance; + qfalse //qboolean hasStorage; + }, + { + BA_H_RTG, //int buildNum; + "rtg", //char *buildName; + "RTG Unit", //char *humanName; + "A portable all-in-one device, featuring a " + "radioisotope thermoelectric generator and a " + "miniaturized version of the Refinery. Does not " + "require power to be built. ^2First RTG Unit does " + "not require build points!", + "team_human_reactor", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + RTG_BP, //int buildPoints; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + RTG_HEALTH, //int health; + 0, //int regenRate; + RTG_SPLASHDAMAGE, //int splashDamage; + RTG_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 500, //int nextthink; + RTG_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + RTG_VALUE, //int value; + qfalse, //qboolean cuboid; + qtrue, //qboolean isPowerSource; + qfalse, //qboolean requiresPower; + RTG_RESISTANCE, //float resistance; + RTG_RESISTANCE, //float surgeResistance; + qtrue //qboolean hasStorage; + }, + { + BA_H_REFINERY, //int buildNum; + "refinery", //char *buildName; + "Refinery", //char *humanName; + "A portable all-in-one device, featuring a " + "radioisotope thermoelectric generator and a " + "miniaturized version of the Refinery. Does not " + "require power to be built. ^1This is the first " + "buildable to be built.", + "team_human_refinery", //char *entityName; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + REFINERY_BP, //int buildPoints; + ( 1 << S2 )|( 1 << S3 ), //int stages + REFINERY_HEALTH, //int health; + 0, //int regenRate; + REFINERY_SPLASHDAMAGE, //int splashDamage; + REFINERY_SPLASHRADIUS, //int splashRadius; + MOD_HSPAWN, //int meansOfDeath; + TEAM_HUMANS, //int team; + ( 1 << WP_HBUILD ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 500, //int nextthink; + REFINERY_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse, //qboolean transparentTest; + qfalse, //qboolean uniqueTest; + REFINERY_VALUE, //int value; + qfalse, //qboolean cuboid; + qfalse, //qboolean isPowerSource; + qtrue, //qboolean requiresPower; + REFINERY_R_IDLE, //float resistance; + REFINERY_R_ACTIVE, //float surgeResistance; + qtrue //qboolean hasStorage; }, { BA_H_CUBOID1, //int buildNum; @@ -610,7 +775,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; 0, //int value; - qtrue //qboolean cuboid; + qtrue, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_H_CUBOID2, //int buildNum; @@ -618,13 +784,13 @@ static const buildableAttributes_t bg_buildableList[ ] = "Glass", //char *humanName; "A cuboid made of a transparent chemical compound. " "Its durability is low compared to other glass-like " - "materials. However it has shown increased strenght " + "materials. However it has shown increased strength " "when formed into a thin pane shape.", "team_human_hcuboid2", //char *entityName; TR_GRAVITY, //trType_t traj; 0.0, //float bounce; 0, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S2 )|( 1 << S3 ), //int stages 0, //int health; 0, //int regenRate; 0, //int splashDamage; @@ -647,7 +813,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; 0, //int value; - qtrue //qboolean cuboid; + qtrue, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_H_CUBOID3, //int buildNum; @@ -661,7 +828,7 @@ static const buildableAttributes_t bg_buildableList[ ] = TR_GRAVITY, //trType_t traj; 0.0, //float bounce; 0, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S3 ), //int stages 0, //int health; 0, //int regenRate; 0, //int splashDamage; @@ -684,7 +851,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; 0, //int value; - qtrue //qboolean cuboid; + qtrue, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_CUBOID1, //int buildNum; @@ -720,7 +888,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; 0, //int value; - qtrue //qboolean cuboid; + qtrue, //qboolean cuboid; + DEFAULT_POWER_SETTINGS }, { BA_A_CUBOID2, //int buildNum; @@ -735,7 +904,7 @@ static const buildableAttributes_t bg_buildableList[ ] = TR_GRAVITY, //trType_t traj; 0.0, //float bounce; 0, //int buildPoints; - ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + ( 1 << S2 )|( 1 << S3 ), //int stages 0, //int health; 0, //int regenRate; 0, //int splashDamage; @@ -758,7 +927,8 @@ static const buildableAttributes_t bg_buildableList[ ] = qfalse, //qboolean transparentTest; qfalse, //qboolean uniqueTest; 0, //int value; - qtrue //qboolean cuboid; + qtrue, //qboolean cuboid; + DEFAULT_POWER_SETTINGS } }; @@ -1283,7 +1453,7 @@ static const classAttributes_t bg_classList[ ] = 80, //int steptime; LEVEL2_SPEED, //float speed; 10.0f, //float acceleration; - 3.0f, //float airAcceleration; + 2.0f, //float airAcceleration; 6.0f, //float friction; 100.0f, //float stopSpeed; 380.0f, //float jumpMagnitude; @@ -1310,7 +1480,7 @@ static const classAttributes_t bg_classList[ ] = 80, //int steptime; LEVEL2_UPG_SPEED, //float speed; 10.0f, //float acceleration; - 3.0f, //float airAcceleration; + 2.0f, //float airAcceleration; 6.0f, //float friction; 100.0f, //float stopSpeed; 380.0f, //float jumpMagnitude; @@ -1419,9 +1589,9 @@ static const classAttributes_t bg_classList[ ] = 0.002f, //float bob; 1.0f, //float bobCycle; 100, //int steptime; - 1.0f, //float speed; + 1.1f, //float speed; 10.0f, //float acceleration; - 1.0f, //float airAcceleration; + 1.5f, //float airAcceleration; 6.0f, //float friction; 100.0f, //float stopSpeed; 220.0f, //float jumpMagnitude; @@ -4301,7 +4471,7 @@ const cuboidAttributes_t BG_CuboidTypes [] = 80, // float hppv CBHPT_PLAIN, // int hpt 3.0, // float bppv - 20, // int buildrate + 30, // int buildrate 5000, // int minbt qfalse, // qboolean regen; 0, // int regenspeed @@ -4325,7 +4495,7 @@ const cuboidAttributes_t BG_CuboidTypes [] = 52, // float hppv CBHPT_PANES, // int hpt 1.5, // float bppv - 15, // int buildrate + 25, // int buildrate 6500, // int minbt qfalse, // qboolean regen; 0, // int regenspeed @@ -4349,7 +4519,7 @@ const cuboidAttributes_t BG_CuboidTypes [] = 50, // float hppv CBHPT_PLAIN, // int hpt 4.0, // float bppv - 10, // int buildrate + 15, // int buildrate 8000, // int minbt qfalse, // qboolean regen; 0, // int regenspeed diff --git a/src/game/bg_public.h b/src/game/bg_public.h index e06015d..cbaf90a 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -280,12 +280,11 @@ typedef enum PERS_CREDIT, // human credit PERS_QUEUEPOS, // position in the spawn queue PERS_NEWWEAPON, // weapon to switch to - PERS_BP, - PERS_MARKEDBP, + PERS_BUILDPOINTS, //zdrytchx: no space in stats, use persistant. This meanas we risk doing a double jump upon spawning but death animations are 1700 msecs long, so technically it's impossible anyway PERS_JUMPTIME, PERS_SPAWNS_IMPLANTED - // netcode has space for 1 more + // netcode has space for 2 more } persEnum_t; #define PS_WALLCLIMBINGFOLLOW 0x00000001 @@ -293,6 +292,21 @@ typedef enum #define PS_NONSEGMODEL 0x00000004 #define PS_SPRINTTOGGLE 0x00000008 +// player_state->misc[] indexes +typedef enum +{ + MISC_INFOHEAD, + MISC_INFO1, + MISC_INFO2, + MISC_INFO3, + + MISC_CUBOID_X, + MISC_CUBOID_Y, + MISC_CUBOID_Z + + // netcode has space for 10 more +} miscEnum_t; + // entityState_t->eFlags // notice that some flags are overlapped, so their meaning depends on context #define EF_DEAD 0x0001 // don't draw a foe marker over players with EF_DEAD @@ -307,7 +321,7 @@ typedef enum // buildable flags: #define EF_B_SPAWNED 0x0008 #define EF_B_POWERED 0x0010 -#define EF_B_MARKED 0x0020 +#define EF_B_SURGE 0x0020 // note: enabled/disabled for Repeaters #define EF_WARN_CHARGE 0x0020 // Lucifer Cannon is about to overcharge #define EF_WALLCLIMB 0x0040 // wall walking @@ -438,6 +452,10 @@ typedef enum BA_H_REACTOR, BA_H_REPEATER, + BA_H_CAPBANK, + BA_H_RTG, + + BA_H_REFINERY, //cuboids must stay in a block #define CUBOID_FIRST BA_H_CUBOID1 @@ -581,7 +599,10 @@ typedef enum EV_ALIEN_HATCH_FAILURE, // when it doesns't work EV_JETPACK_DEACTIVATE, - EV_JETPACK_REFUEL + EV_JETPACK_REFUEL, + + EV_POWER_SWITCH, + EV_POWER_ZAP } entity_event_t; @@ -1068,9 +1089,17 @@ typedef struct qboolean transparentTest; qboolean uniqueTest; - int value; + int value; qboolean cuboid; + + //power grid features + qboolean isPowerSource; + qboolean requiresPower; + float resistance; + float surgeResistance; + + qboolean hasStorage; } buildableAttributes_t; typedef struct diff --git a/src/game/g_active.c b/src/game/g_active.c index 1fa0caa..95f00d6 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -682,20 +682,6 @@ void ClientTimerActions( gentity_t *ent, int msec ) client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; else client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; - - // Let the client know which buildables will be removed by building - for( i = 0; i < MAX_MISC; i++ ) - { - if( i < level.numBuildablesForRemoval ) - client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; - else - client->ps.misc[ i ] = 0; - } - } - else - { - for( i = 0; i < MAX_MISC; i++ ) - client->ps.misc[ i ] = 0; } break; @@ -780,6 +766,55 @@ void ClientTimerActions( gentity_t *ent, int msec ) ent->client->ps.stats[ STAT_SHAKE ] *= 0.77f; if( ent->client->ps.stats[ STAT_SHAKE ] < 0 ) ent->client->ps.stats[ STAT_SHAKE ] = 0; + + // update the currents and voltages + if( BG_GetPlayerWeapon( &client->ps ) == WP_HBUILD ) + { + trace_t tr; + vec3_t viewOrigin, forward, end; + gentity_t *traceEnt; + int head_network = 0, head_entity = 1023; + + BG_GetClientViewOrigin( &client->ps, viewOrigin ); + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + VectorMA( viewOrigin, 200, forward, end ); + + trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); + traceEnt = &g_entities[ tr.entityNum ]; + + head_network = ent->powerNetwork; + + if( tr.fraction < 0.99f ) + { + if( traceEnt->client ) + { + if( traceEnt->client->pers.teamSelection == TEAM_HUMANS && + traceEnt->client->ps.stats[ STAT_HEALTH ] >= 0 ) + { + head_entity = traceEnt->s.number; + client->ps.misc[ MISC_INFO1 ] = traceEnt->client->ps.stats[ STAT_HEALTH ]; + + if( BG_GetPlayerWeapon( &traceEnt->client->ps ) == WP_HBUILD ) + client->ps.misc[ MISC_INFO2 ] = traceEnt->client->ps.persistant[ PERS_BUILDPOINTS ]; + else + client->ps.misc[ MISC_INFO2 ] = -1; + } + } + else if( traceEnt->s.eType == ET_BUILDABLE && + traceEnt->health > 0 && traceEnt->buildableTeam == TEAM_HUMANS && + ( traceEnt->isPowerSource || traceEnt->requiresPower ) ) + { + head_network = traceEnt->powerNetwork; + head_entity = traceEnt->s.number; + client->ps.misc[ MISC_INFO1 ] = traceEnt->current * 1000; + client->ps.misc[ MISC_INFO2 ] = traceEnt->voltage * 1000; + client->ps.misc[ MISC_INFO3 ] = traceEnt->storedBP * 10; + } + } + + client->ps.misc[ MISC_INFOHEAD ] = ( head_network & 1023 ) | ( head_entity & 1023 ) << 10; + + } } while( client->time1000 >= 1000 ) @@ -1769,16 +1804,11 @@ void ClientThink_real( gentity_t *ent ) vec3_t eyes, view, point; gentity_t *traceEnt; -#define USE_OBJECT_RANGE 64 - - int entityList[ MAX_GENTITIES ]; - vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; - vec3_t mins, maxs; - int i, num; +#define USE_OBJECT_RANGE 100 // look for object infront of player AngleVectors( client->ps.viewangles, view, NULL, NULL ); - BG_GetClientViewOrigin( &client->ps, eyes ); // !@#CUBOID + BG_GetClientViewOrigin( &client->ps, eyes ); VectorMA( eyes, USE_OBJECT_RANGE, view, point ); trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); @@ -1786,50 +1816,29 @@ void ClientThink_real( gentity_t *ent ) if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - else + else if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - //no entity in front of player - do a small area search - - VectorAdd( client->ps.origin, range, maxs ); - VectorSubtract( client->ps.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) + if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], + client->pers.credit, + g_alienStage.integer ) ) { - traceEnt = &g_entities[ entityList[ i ] ]; - - if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) - { - traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - break; - } + //no nearby objects and alien - show class menu + G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); } - - if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + else { - if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], - client->pers.credit, - g_alienStage.integer ) ) - { - //no nearby objects and alien - show class menu - G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); - } - else - { - //flash frags - G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); - } + //flash frags + G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); } } } - client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, - client->ps.stats[ STAT_TEAM ] ); - client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, - client->ps.stats[ STAT_TEAM ] ); - - if( client->ps.persistant[ PERS_BP ] < 0 ) - client->ps.persistant[ PERS_BP ] = 0; + if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + { + client->ps.persistant[ PERS_BUILDPOINTS ] = G_GetBuildPoints( client->ps.origin, client->ps.stats[ STAT_TEAM ] ); + if( client->ps.persistant[ PERS_BUILDPOINTS ] < 0 ) + client->ps.persistant[ PERS_BUILDPOINTS ] = 0; + } // perform once-a-second actions ClientTimerActions( ent, msec ); diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index 0039ccd..8447e84 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -111,589 +111,715 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, return NULL; } -#define POWER_REFRESH_TIME 2000 +/* +================== +G_GetBuildPoints + +Get the number of build points from a position +================== +*/ +int G_GetBuildPoints( const vec3_t pos, team_t team ) +{ + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + return 0; + } + else if( team == TEAM_ALIENS ) + { + return level.alienBuildPoints; + } + else if( team == TEAM_HUMANS ) + { + return 0xDEADBEE; //humans use the material system + } + + return 0; +} /* ================ -G_FindPower +G_IsDCCBuilt -attempt to find power for self, return qtrue if successful +See if any powered DCC exists ================ */ -qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ) +qboolean G_IsDCCBuilt( void ) { - int i, j; - gentity_t *ent, *ent2; - gentity_t *closestPower = NULL; - int distance = 0; - int minDistance = REPEATER_BASESIZE + 1; - vec3_t temp_v; - - if( self->buildableTeam != TEAM_HUMANS ) - return qfalse; + int i; + gentity_t *ent; + static gentity_t *cache = NULL; + + if( cache && cache->inuse && cache->s.eType == ET_BUILDABLE && + cache->s.modelindex == BA_H_DCC && cache->spawned && + cache->powered && cache->health >= 0 ) + return qtrue; - // Reactor is always powered - if( self->s.modelindex == BA_H_REACTOR ) + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { - self->parentNode = self; + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex != BA_H_DCC ) + continue; + + if( !ent->spawned ) + continue; + + if( ent->health <= 0 ) + continue; + + if( !ent->powered ) + continue; + + cache = ent; return qtrue; } - // Handle repeaters - if( self->s.modelindex == BA_H_REPEATER ) - { - self->parentNode = G_Reactor( ); + return qfalse; +} - return self->parentNode != NULL; - } - // Iterate through entities +/* +================ +G_IsRTGBuilt + +See if any RTG exists +================ +*/ +qboolean G_IsRTGBuilt( void ) +{ + int i; + gentity_t *ent; + static gentity_t *cache = NULL; + + if( cache && cache->inuse && cache->s.eType == ET_BUILDABLE && + cache->s.modelindex == BA_H_DCC && cache->spawned && + cache->health >= 0 ) + return qtrue; + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { + if( !ent->r.linked ) + continue; + if( ent->s.eType != ET_BUILDABLE ) continue; - // If entity is a power item calculate the distance to it - if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && - ( searchUnspawned || ent->spawned ) && ent->powered && ent->health > 0 ) - { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); - distance = VectorLength( temp_v ); - - // Always prefer a reactor if there is one in range - if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) - { - // Only power as much BP as the reactor can hold - if( self->s.modelindex != BA_NONE ) - { - int buildPoints = g_humanBuildPoints.integer; - - // Scan the buildables in the reactor zone - for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) - { - gentity_t *powerEntity; - - if( ent2->s.eType != ET_BUILDABLE ) - continue; - - if( ent2 == self ) - continue; + if( ent->s.modelindex != BA_H_RTG ) + continue; - powerEntity = ent2->parentNode; + if( !ent->spawned ) + continue; - if( powerEntity && powerEntity->s.modelindex == BA_H_REACTOR && ( powerEntity == ent ) ) - { - buildPoints -= BG_Buildable( ent2->s.modelindex, ent2->cuboidSize )->buildPoints; - } - } + if( ent->health <= 0 ) + continue; - buildPoints -= level.humanBuildPointQueue; + cache = ent; - buildPoints -= BG_Buildable( self->s.modelindex, self->cuboidSize )->buildPoints; + return qtrue; + } - if( buildPoints >= 0 ) - { - self->parentNode = ent; - return qtrue; - } - else - { - // a buildable can still be built if it shares BP from two zones + return qfalse; +} - // TODO: handle combined power zones here - } - } +/* +================ +G_Overmind - // Dummy buildables don't need to look for zones - else - { - self->parentNode = ent; - return qtrue; - } - } - else if( distance < minDistance ) - { - // It's a repeater, so check that enough BP will be available to power - // the buildable but only if self is a real buildable +Since there's only one overmind and we quite often want to find it, cache the +results, but check it for validity each time - if( self->s.modelindex != BA_NONE ) - { - int buildPoints = g_humanRepeaterBuildPoints.integer; +The code here will break if more than one overmind is allowed, even +if one of them is dead/unspawned +================ +*/ +static gentity_t *G_FindBuildable( buildable_t buildable ); - // Scan the buildables in the repeater zone - for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) - { - gentity_t *powerEntity; +gentity_t *G_Overmind( void ) +{ + static gentity_t *om; - if( ent2->s.eType != ET_BUILDABLE ) - continue; + // If cache becomes invalid renew it + if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND ) + om = G_FindBuildable( BA_A_OVERMIND ); - if( ent2 == self ) - continue; + // If we found it and it's alive, return it + if( om && om->spawned && om->health > 0 ) + return om; - powerEntity = ent2->parentNode; + return NULL; +} - if( powerEntity && powerEntity->s.modelindex == BA_H_REPEATER && ( powerEntity == ent ) ) - { - buildPoints -= BG_Buildable( ent2->s.modelindex, ent->cuboidSize )->buildPoints; - } - } +/* +================ +G_FindCreep - if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) - buildPoints -= level.buildPointZones[ ent->buildPointZone ].queuedBuildPoints; +attempt to find creep for self, return qtrue if successful +================ +*/ +qboolean G_FindCreep( gentity_t *self ) +{ + int i; + gentity_t *ent; + gentity_t *closestSpawn = NULL; + int distance = 0; + int minDistance = 10000; + vec3_t temp_v; - buildPoints -= BG_Buildable( self->s.modelindex, self->cuboidSize )->buildPoints; + //don't check for creep if flying through the air + if( self->s.groundEntityNum == -1 ) + return qtrue; - if( buildPoints >= 0 ) - { - closestPower = ent; - minDistance = distance; - } - else - { - // a buildable can still be built if it shares BP from two zones + //if self does not have a parentNode or it's parentNode is invalid find a new one + if( self->client || self->parentNode == NULL || !self->parentNode->inuse || + self->parentNode->health <= 0 ) + { + for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; - // TODO: handle combined power zones here - } - } - else + if( ( ent->s.modelindex == BA_A_SPAWN || + ent->s.modelindex == BA_A_OVERMIND ) && + ent->spawned && ent->health > 0 ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < minDistance ) { - // Dummy buildables don't need to look for zones - closestPower = ent; + closestSpawn = ent; minDistance = distance; } } } + + if( minDistance <= CREEP_BASESIZE ) + { + if( !self->client ) + self->parentNode = closestSpawn; + return qtrue; + } + else + return qfalse; } - self->parentNode = closestPower; - return self->parentNode != NULL; + if( self->client ) + return qfalse; + + //if we haven't returned by now then we must already have a valid parent + return qtrue; } /* ================ -G_PowerEntityForPoint +G_IsCreepHere -Simple wrapper to G_FindPower to find the entity providing -power for the specified point +simple wrapper to G_FindCreep to check if a location has creep ================ */ -gentity_t *G_PowerEntityForPoint( const vec3_t origin ) +static qboolean G_IsCreepHere( vec3_t origin ) { gentity_t dummy; + memset( &dummy, 0, sizeof( gentity_t ) ); + dummy.parentNode = NULL; - dummy.buildableTeam = TEAM_HUMANS; dummy.s.modelindex = BA_NONE; VectorCopy( origin, dummy.s.origin ); - if( G_FindPower( &dummy, qfalse ) ) - return dummy.parentNode; - else - return NULL; -} - -/* -================ -G_PowerEntityForEntity - -Simple wrapper to G_FindPower to find the entity providing -power for the specified entity -================ -*/ -gentity_t *G_PowerEntityForEntity( gentity_t *ent ) -{ - if( G_FindPower( ent, qfalse ) ) - return ent->parentNode; - return NULL; + return G_FindCreep( &dummy ); } /* ================ -G_IsPowered +G_CreepSlow -Check if a location has power, returning the entity type -that is providing it +Set any nearby humans' SS_CREEPSLOWED flag ================ */ -buildable_t G_IsPowered( vec3_t origin ) +static void G_CreepSlow( gentity_t *self ) { - gentity_t *ent = G_PowerEntityForPoint( origin ); - - if( ent ) - return ent->s.modelindex; - else - return BA_NONE; -} + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy; + buildable_t buildable = self->s.modelindex; + float creepSize = (float)BG_Buildable( buildable, self->cuboidSize )->creepSize; + VectorSet( range, creepSize, creepSize, creepSize ); -/* -================== -G_GetBuildPoints + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); -Get the number of build points from a position -================== -*/ -int G_GetBuildPoints( const vec3_t pos, team_t team ) -{ - if( G_TimeTilSuddenDeath( ) <= 0 ) - { - return 0; - } - else if( team == TEAM_ALIENS ) - { - return level.alienBuildPoints; - } - else if( team == TEAM_HUMANS ) + //find humans + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) { - gentity_t *powerPoint = G_PowerEntityForPoint( pos ); + enemy = &g_entities[ entityList[ i ] ]; - if( powerPoint && powerPoint->s.modelindex == BA_H_REACTOR ) - return level.humanBuildPoints; + if( enemy->flags & FL_NOTARGET ) + continue; - if( powerPoint && powerPoint->s.modelindex == BA_H_REPEATER && - powerPoint->usesBuildPointZone && level.buildPointZones[ powerPoint->buildPointZone ].active ) + if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && + enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) { - return level.buildPointZones[ powerPoint->buildPointZone ].totalBuildPoints - - level.buildPointZones[ powerPoint->buildPointZone ].queuedBuildPoints; + enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; + enemy->client->lastCreepSlowTime = level.time; } - - // Return the BP of the main zone by default - return level.humanBuildPoints; } - - return 0; } /* -================== -G_GetMarkedBuildPoints - -Get the number of marked build points from a position -================== +================ +G_ScanPowerGrid + +Recursively finds all power entities reachable from the specified entity +================ */ -int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ) +static struct { - gentity_t *ent; - int i; - int sum = 0; + int networkID; + + gentity_t *load[ MAX_GENTITIES ]; + int loadCount; + + gentity_t *sources[ MAX_GENTITIES ]; + int sourceCount; + + qboolean visited[ MAX_GENTITIES ]; +} grid; - if( G_TimeTilSuddenDeath( ) <= 0 ) - return 0; +void G_ScanPowerGrid( gentity_t *this ) +{ + int i; + int nextList[ MAX_GENTITIES ], nextCount; + gentity_t *next; + vec3_t mins, maxs; + float range; - if( !g_markDeconstruct.integer ) - return 0; + switch( this->s.modelindex ) + { + case BA_H_REACTOR: + case BA_H_CAPBANK: + case BA_H_RTG: + range = REACTOR_BASESIZE; + break; + case BA_H_REPEATER: + range = REPEATER_BASESIZE; + break; + } - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + for( i = 0; i < 3; i++ ) { - if( ent->s.eType != ET_BUILDABLE ) + mins[ i ] = this->s.origin[ i ] - range; + maxs[ i ] = this->s.origin[ i ] + range; + } + + nextCount = trap_EntitiesInBox( mins, maxs, nextList, MAX_GENTITIES ); + + for( i = 0; i < nextCount; i++ ) + { + if( grid.visited[ nextList[ i ] ] ) continue; - - if( team == TEAM_HUMANS && - ent->s.modelindex != BA_H_REACTOR && - ent->s.modelindex != BA_H_REPEATER && - ent->parentNode != G_PowerEntityForPoint( pos ) ) + grid.visited[ nextList[ i ] ] = qtrue; + + next = g_entities + nextList[ i ]; + + if( next->s.eType != ET_BUILDABLE ) + { + //let ckits know in which network's range they're in + if( next->client && + next->health >= 0 && + next->s.weapon == WP_HBUILD ) + next->powerNetwork = grid.networkID; continue; + } - if( !ent->inuse ) + if( next->health <= 0 ) continue; - - if( ent->health <= 0 ) + if( next->buildableTeam != TEAM_HUMANS ) continue; - - if( ent->buildableTeam != team ) + + //repeater just extends the power grid + //it does not provide or consume power + if( next->spawned && next->s.modelindex == BA_H_REPEATER ) + { + //switched off + if( !next->active ) + continue; + + next->powerNetwork = grid.networkID; + G_ScanPowerGrid( next ); continue; + } - if( ent->deconstruct ) - sum += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; + if( !next->isPowerSource && !next->requiresPower ) + continue; + + next->powerNetwork = grid.networkID; + if( next->isPowerSource ) + { + //switched off + if( !next->active ) + continue; + grid.sources[ grid.sourceCount++ ] = next; + } + else + grid.load[ grid.loadCount++ ] = next; } - - return sum; } + /* -================== -G_InPowerZone +================ +G_CalculatePowerGrid + +This function takes the listed power sources (with voltages V and +internal resistances Rs) and load (with resistances R), constructs the +following electrical circuit and solves it: + + Legend: + -(-+)-- voltage source + -/\/\/- resistor + |- ground + + V1 Rs1 I1 R1 + +--(-+)--/\/\/-->-+ +--/\/\/--+ + | V2 Rs2 I2| I | R2 | + |--+--(-+)--/\/\/-->-+-->--+--/\/\/--+--| + | | | | + ... ... ... ... + | Vn Rsn In| | Rn | + +--(-+)--/\/\/-->-+ +--/\/\/--+ -See if a buildable is inside of another power zone. -Return pointer to provider if so. -It's different from G_FindPower because FindPower for -providers will find themselves. -(This doesn't check if power zones overlap) -================== +================ */ -gentity_t *G_InPowerZone( gentity_t *self ) +void G_CalculatePowerGrid( void ) { - int i; - gentity_t *ent; - int distance; - vec3_t temp_v; + int i, j; + gentity_t *ent; - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + float nGl, nGs, nG, nR; + float URs, U2, Is; + + // the net load conductance (resistors in parallel) + for( nGl = 0.0f, i = 0; i < grid.loadCount; i++ ) + nGl += 1.0f / grid.load[ i ]->resistance; + + // the net source conductance (resistors in parallel) + for( nGs = 0.0f, i = 0; i < grid.sourceCount; i++ ) + nGs += 1.0f / grid.sources[ i ]->resistance; + + // solve the circuit using the superposition theorem + for( i = 0; i < grid.sourceCount; i++ ) { - if( ent->s.eType != ET_BUILDABLE ) - continue; - - if( ent == self ) - continue; - - if( !ent->spawned ) - continue; - - if( ent->health <= 0 ) - continue; - - // if entity is a power item calculate the distance to it - if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && - ent->spawned && ent->powered ) - { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); - distance = VectorLength( temp_v ); + ent = grid.sources[ i ]; + + // net load + other sources conductance + nG = nGl + nGs - 1.0f / ent->resistance; + + // net resistance + nR = 1.0f / nG + ent->resistance; + + // current flowing through the source + Is = ent->voltage / nR; + ent->current += ent->voltage / nR; - if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) - return ent; - else if( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE ) - return ent; - } + // voltage drop on source's internal resistance + URs = Is * ent->resistance; + + // voltage drop on other sources or the entire load + U2 = ent->voltage - URs; + + // current flowing through other sources + for( j = 0; j < grid.sourceCount; j++ ) + if( i != j ) + grid.sources[ j ]->current -= U2 / grid.sources[ j ]->resistance; + + // current flowing through parts of the load + for( j = 0; j < grid.loadCount; j++ ) + grid.load[ j ]->current += U2 / grid.load[ j ]->resistance; } - - return NULL; } /* ================ -G_FindDCC +G_UpdatePowerGrid -attempt to find a controlling DCC for self, return number found +Recalculate the entire power grid ================ */ -int G_FindDCC( gentity_t *self ) +void G_UpdatePowerGrid( float dt ) { - int i; + int i; gentity_t *ent; - int distance = 0; - vec3_t temp_v; - int foundDCC = 0; - if( self->buildableTeam != TEAM_HUMANS ) - return 0; + // reset all ckits + for( i = 0; i < MAX_CLIENTS; i++ ) + g_entities[ i ].powerNetwork = 0; - //iterate through entities - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities && foundDCC < MAX_DCS_PER_BUILDABLE; i++, ent++ ) + // reset all power entities + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { + ent = g_entities + i; + if( ent->s.eType != ET_BUILDABLE ) continue; + if( ent->buildableTeam != TEAM_HUMANS ) + continue; - //if entity is a dcc calculate the distance to it - if( ent->s.modelindex == BA_H_DCC && ent->spawned ) + ent->powerNetwork = 0; + ent->current = 0.0f; + + if( !ent->spawned ) { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); - distance = VectorLength( temp_v ); - if( distance < DC_RANGE && ent->powered ) - { - foundDCC++; - } + ent->resistance = PREBUILD_RESISTANCE; + continue; } + + if( ent->s.modelindex == BA_H_REACTOR || + ent->s.modelindex == BA_H_RTG ) + ent->voltage = POWER_VOLTAGE * g_voltageModifier.value; + + if( !ent->requiresPower || ent->isPowerSource ) + continue; + + if( ent->surge ) + ent->resistance = BG_Buildable( ent->s.modelindex, NULL )->surgeResistance; + else + ent->resistance = BG_Buildable( ent->s.modelindex, NULL )->resistance; } - return foundDCC; -} + // this table will be used throughout the following loop and its recursive calls + memset( grid.visited, 0, sizeof( grid.visited ) ); + + // find an unvisited power source and make a list of all power sources + // and receivers reachable from it for G_CalculatePowerGrid + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + if( grid.visited[ i ] ) + continue; + + ent = g_entities + i; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + if( !ent->spawned ) + continue; + if( ent->health <= 0 ) + continue; + if( ent->buildableTeam != TEAM_HUMANS ) + continue; + if( !ent->isPowerSource ) + continue; + if( !ent->active ) + continue; -/* -================ -G_IsDCCBuilt + // unique network id + grid.networkID = ent->s.number; + ent->powerNetwork = grid.networkID; -See if any powered DCC exists -================ -*/ -qboolean G_IsDCCBuilt( void ) -{ - int i; - gentity_t *ent; + // traverse the world and find all reachable power entities + grid.loadCount = 0; + grid.sourceCount = 0; - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + // add this source to the list + grid.visited[ i ] = qtrue; + grid.sources[ grid.sourceCount++ ] = ent; + + // scan recursively + G_ScanPowerGrid( ent ); + + // calculate the power grid + G_CalculatePowerGrid( ); + } + + // calculate voltages, power levels, etc. + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { + ent = g_entities + i; + if( ent->s.eType != ET_BUILDABLE ) continue; - - if( ent->s.modelindex != BA_H_DCC ) + if( ent->buildableTeam != TEAM_HUMANS ) continue; - if( !ent->spawned ) - continue; + if( ent->isPowerSource ) + { + if( ent->active && ent->s.modelindex == BA_H_CAPBANK ) + ent->voltage -= ent->current * dt / CAPBANK_CAPACITY; - if( ent->health <= 0 ) - continue; + //zapping effect + #define MIN_ZAP_CURRENT 2.0f + #define ZAP_CHANCE_FACTOR 0.01f + if( ent->current > MIN_ZAP_CURRENT ) + { + float chance; + + chance = ( ent->current - MIN_ZAP_CURRENT ) * ZAP_CHANCE_FACTOR; + if( chance > 0.2f ) + chance = 0.2f; - return qtrue; + chance = 1.0f - chance; + if( random() > chance ) + G_AddEvent( ent, EV_POWER_ZAP, 0 ); + } + } + else + ent->voltage = ent->current * ent->resistance; } - - return qfalse; } /* ================ -G_Reactor -G_Overmind - -Since there's only one of these and we quite often want to find them, cache the -results, but check them for validity each time +G_SetupPowerEntity -The code here will break if more than one reactor or overmind is allowed, even -if one of them is dead/unspawned +Called when a Human buildable finishes spawning and needs power grid +related variables to be set accordingly ================ */ -static gentity_t *G_FindBuildable( buildable_t buildable ); - -gentity_t *G_Reactor( void ) -{ - static gentity_t *rc; - - // If cache becomes invalid renew it - if( !rc || rc->s.eType != ET_BUILDABLE || rc->s.modelindex != BA_H_REACTOR ) - rc = G_FindBuildable( BA_H_REACTOR ); - - // If we found it and it's alive, return it - if( rc && rc->spawned && rc->health > 0 ) - return rc; - - return NULL; -} - -gentity_t *G_Overmind( void ) +void G_SetupPowerEntity( gentity_t *built ) { - static gentity_t *om; - - // If cache becomes invalid renew it - if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND ) - om = G_FindBuildable( BA_A_OVERMIND ); - - // If we found it and it's alive, return it - if( om && om->spawned && om->health > 0 ) - return om; + built->requiresPower = BG_Buildable( built->s.modelindex, NULL )->requiresPower; + built->isPowerSource = BG_Buildable( built->s.modelindex, NULL )->isPowerSource; + built->resistance = BG_Buildable( built->s.modelindex, NULL )->resistance; - return NULL; + if( built->isPowerSource ) + { + switch( built->s.modelindex ) + { + case BA_H_REACTOR: + built->resistance = REACTOR_RESISTANCE; + break; + case BA_H_CAPBANK: + built->voltage = 0.0f; //spawn discharged + built->resistance = CAPBANK_RESISTANCE; + case BA_H_RTG: + built->resistance = RTG_RESISTANCE; + break; + } + } } /* ================ -G_FindCreep +G_PowerForPoint -attempt to find creep for self, return qtrue if successful +Returns to which network ID this point belongs ================ */ -qboolean G_FindCreep( gentity_t *self ) + +int G_PowerForPoint( vec3_t point ) { - int i; + int i; + int list[ MAX_GENTITIES ], count; gentity_t *ent; - gentity_t *closestSpawn = NULL; - int distance = 0; - int minDistance = 10000; - vec3_t temp_v; + vec3_t mins, maxs; + float range; +/* + switch( this->s.modelindex ) + { - //don't check for creep if flying through the air - if( self->s.groundEntityNum == -1 ) - return qtrue; + }*/ + range = REACTOR_BASESIZE; - //if self does not have a parentNode or it's parentNode is invalid find a new one - if( self->client || self->parentNode == NULL || !self->parentNode->inuse || - self->parentNode->health <= 0 ) + for( i = 0; i < 3; i++ ) { - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + mins[ i ] = point[ i ] - range; + maxs[ i ] = point[ i ] + range; + } + + count = trap_EntitiesInBox( mins, maxs, list, MAX_GENTITIES ); + + for( i = 0; i < count; i++ ) + { + ent = g_entities + list[ i ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + if( ent->buildableTeam != TEAM_HUMANS ) + continue; + if( ent->health <= 0 ) + continue; + if( !ent->spawned ) + continue; + if( !ent->powerNetwork ) + continue; + + switch( ent->s.modelindex ) { - if( ent->s.eType != ET_BUILDABLE ) + case BA_H_REACTOR: + case BA_H_CAPBANK: + case BA_H_RTG: + range = REACTOR_BASESIZE; + break; + case BA_H_REPEATER: + range = REPEATER_BASESIZE; + break; + default: continue; - - if( ( ent->s.modelindex == BA_A_SPAWN || - ent->s.modelindex == BA_A_OVERMIND ) && - ent->spawned && ent->health > 0 ) - { - VectorSubtract( self->s.origin, ent->s.origin, temp_v ); - distance = VectorLength( temp_v ); - if( distance < minDistance ) - { - closestSpawn = ent; - minDistance = distance; - } - } } + + if( Distance( ent->s.origin, point ) > range ) + continue; - if( minDistance <= CREEP_BASESIZE ) - { - if( !self->client ) - self->parentNode = closestSpawn; - return qtrue; - } - else - return qfalse; + return ent->powerNetwork; } - - if( self->client ) - return qfalse; - - //if we haven't returned by now then we must already have a valid parent - return qtrue; + return 0; } /* ================ -G_IsCreepHere +NOTES TO G_CheckPower AND G_Surge -simple wrapper to G_FindCreep to check if a location has creep +Make sure that a buildable's resistance NEVER depends on its power +state. Failure to ensure that will result in feedback loops in the +power grid and general weirdness. ================ */ -static qboolean G_IsCreepHere( vec3_t origin ) -{ - gentity_t dummy; - - memset( &dummy, 0, sizeof( gentity_t ) ); - - dummy.parentNode = NULL; - dummy.s.modelindex = BA_NONE; - VectorCopy( origin, dummy.s.origin ); - - return G_FindCreep( &dummy ); -} /* ================ -G_CreepSlow +G_CheckPower -Set any nearby humans' SS_CREEPSLOWED flag +(Helper for Human buildable think functions) +Checks if there's enough power for the buildable to idle ================ */ -static void G_CreepSlow( gentity_t *self ) +qboolean G_CheckPower( gentity_t *self, float min_current ) { - int entityList[ MAX_GENTITIES ]; - vec3_t range; - vec3_t mins, maxs; - int i, num; - gentity_t *enemy; - buildable_t buildable = self->s.modelindex; - float creepSize = (float)BG_Buildable( buildable, self->cuboidSize )->creepSize; - - VectorSet( range, creepSize, creepSize, creepSize ); - - VectorAdd( self->s.origin, range, maxs ); - VectorSubtract( self->s.origin, range, mins ); - - //find humans - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) + self->surge = qfalse; + self->surgePowered = qfalse; + self->powered = qtrue; + + if( self->current < min_current ) { - enemy = &g_entities[ entityList[ i ] ]; + self->powered = qfalse; + return qfalse; + } + + return qtrue; +} - if( enemy->flags & FL_NOTARGET ) - continue; +/* +================ +G_Surge - if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && - enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) - { - enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; - enemy->client->lastCreepSlowTime = level.time; - } - } +(Helper for Human buildable think functions) +Checks if there's enough power for the buildable to perform its action +================ +*/ +qboolean G_Surge( gentity_t *self, float surge_current ) +{ + self->surge = qtrue; + + if( self->current < surge_current ) + return qfalse; + + self->surgePowered = qtrue; + return qtrue; } /* @@ -1536,42 +1662,6 @@ void ATrapper_Think( gentity_t *self ) //================================================================================== - - - -/* -================ -G_SuicideIfNoPower - -Destroy human structures that have been unpowered too long -================ -*/ -static qboolean G_SuicideIfNoPower( gentity_t *self ) -{ - if( self->buildableTeam != TEAM_HUMANS ) - return qfalse; - - if( !self->powered ) - { - // if the power hasn't reached this buildable for some time, then destroy the buildable - if( self->count == 0 ) - self->count = level.time; - else if( ( level.time - self->count ) >= HUMAN_BUILDABLE_INACTIVE_TIME ) - { - if( self->parentNode ) - G_Damage( self, NULL, g_entities + self->parentNode->killedBy, - NULL, NULL, self->health, 0, MOD_NOCREEP ); - else - G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); - return qtrue; - } - } - else - self->count = 0; - - return qfalse; -} - /* ================ G_IdlePowerState @@ -1594,13 +1684,9 @@ static void G_IdlePowerState( gentity_t *self ) } - - //================================================================================== - - /* ================ HSpawn_Disappear @@ -1666,7 +1752,6 @@ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->die = nullDieFunction; self->killedBy = attacker - g_entities; - self->powered = qfalse; //free up power self->s.eFlags &= ~EF_FIRING; //prevent any firing effects if( self->spawned ) @@ -1694,36 +1779,30 @@ void HSpawn_Think( gentity_t *self ) { gentity_t *ent; - // set parentNode - self->powered = G_FindPower( self, qfalse ); - - if( G_SuicideIfNoPower( self ) ) + if( !self->spawned || self->health <= 0 ) return; - - if( self->spawned ) + + //only suicide if at rest + if( self->s.groundEntityNum ) { - //only suicide if at rest - if( self->s.groundEntityNum ) + if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, + self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) { - if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, - self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) + // If the thing blocking the spawn is a buildable, kill it. + // If it's part of the map, kill self. + if( ent->s.eType == ET_BUILDABLE ) { - // If the thing blocking the spawn is a buildable, kill it. - // If it's part of the map, kill self. - if( ent->s.eType == ET_BUILDABLE ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); - G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); - } - else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) - { - G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); - return; - } - - if( ent->s.eType == ET_CORPSE ) - G_FreeEntity( ent ); //quietly remove + G_Damage( ent, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); } + else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove } } @@ -1735,115 +1814,83 @@ void HSpawn_Think( gentity_t *self ) //================================================================================== - - - /* ================ -HRepeater_Die +HRepeater_Think -Called when a repeater dies +Think function for Human Repeater ================ */ -static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) -{ - G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); - G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); - - self->die = nullDieFunction; - self->killedBy = attacker - g_entities; - self->powered = qfalse; //free up power - self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - if( self->spawned ) - { - self->think = HSpawn_Blast; - self->nextthink = level.time + HUMAN_DETONATION_DELAY; - } - else - { - self->think = HSpawn_Disappear; - self->nextthink = level.time; //blast immediately - } +void HRepeater_Think( gentity_t *self ) +{ + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; - G_LogDestruction( self, attacker, mod ); + if( !self->spawned || self->health <= 0 ) + return; - if( self->usesBuildPointZone ) - { - buildPointZone_t *zone = &level.buildPointZones[self->buildPointZone]; + self->powered = self->active && ( self->powerNetwork != 0 ); - zone->active = qfalse; - self->usesBuildPointZone = qfalse; - } + G_IdlePowerState( self ); } + /* ================ -HRepeater_Think +HRepeater_Die -Think for human power repeater +Called when a repeater dies ================ */ -void HRepeater_Think( gentity_t *self ) +static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) { - int i; - gentity_t *powerEnt; - buildPointZone_t *zone; + G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); - self->powered = G_FindPower( self, qfalse ); + self->die = nullDieFunction; + self->killedBy = attacker - g_entities; + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - powerEnt = G_InPowerZone( self ); - if( powerEnt != NULL ) + if( self->spawned ) { - // If the repeater is inside of another power zone then suicide - // Attribute death to whoever built the reactor if that's a human, - // which will ensure that it does not queue the BP - if( powerEnt->builtBy >= 0 ) - G_Damage( self, NULL, g_entities + powerEnt->builtBy, NULL, NULL, self->health, 0, MOD_SUICIDE ); - else - G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); - return; + self->think = HSpawn_Blast; + self->nextthink = level.time + HUMAN_DETONATION_DELAY; } - - G_IdlePowerState( self ); - - // Initialise the zone once the repeater has spawned - if( self->spawned && ( !self->usesBuildPointZone || !level.buildPointZones[ self->buildPointZone ].active ) ) + else { - // See if a free zone exists - for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) - { - zone = &level.buildPointZones[ i ]; - - if( !zone->active ) - { - // Initialise the BP queue with no BP queued - zone->queuedBuildPoints = 0; - zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; - zone->nextQueueTime = level.time; - zone->active = qtrue; - - self->buildPointZone = zone - level.buildPointZones; - self->usesBuildPointZone = qtrue; - - break; - } - } + self->think = HSpawn_Disappear; + self->nextthink = level.time; //blast immediately } - self->nextthink = level.time + POWER_REFRESH_TIME; + G_LogDestruction( self, attacker, mod ); } /* ================ -HRepeater_Use +HSwitchable_Use -Use for human power repeater +Use for switchable buildings ================ */ -void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +void HSwitchable_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) { - if( self->health <= 0 || !self->spawned ) + if( !self->spawned || self->health <= 0 ) + return; + + if( !other || !other->client ) + return; + + // ckits and blasters switch the building + if( other->s.weapon == WP_HBUILD || + other->s.weapon == WP_BLASTER ) + { + self->active ^= 1; + G_AddEvent( self, EV_POWER_SWITCH, 0 ); + return; + } + + // not powered + if( !self->powerNetwork ) return; if( other && other->client ) @@ -1869,8 +1916,16 @@ void HReactor_Think( gentity_t *self ) vec3_t mins, maxs; int i, num; gentity_t *enemy, *tent; + qboolean fired = qfalse; + + if( !self->spawned || self->health <= 0 ) + return; + + self->powered = self->active; + + G_IdlePowerState( self ); - if( self->dcc ) + if( G_IsDCCBuilt( ) ) { VectorAdd( self->s.origin, dccrange, maxs ); VectorSubtract( self->s.origin, dccrange, mins ); @@ -1881,54 +1936,62 @@ void HReactor_Think( gentity_t *self ) VectorSubtract( self->s.origin, range, mins ); } - if( self->spawned && ( self->health > 0 ) ) + // Creates a tesla trail for every target + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) { - qboolean fired = qfalse; - - // Creates a tesla trail for every target - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - enemy = &g_entities[ entityList[ i ] ]; - if( !enemy->client || - enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) - continue; - if( enemy->flags & FL_NOTARGET ) - continue; + enemy = &g_entities[ entityList[ i ] ]; + if( !enemy->client || + enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) + continue; + if( enemy->flags & FL_NOTARGET ) + continue; - tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); - tent->s.generic1 = self->s.number; //src - tent->s.clientNum = enemy->s.number; //dest - VectorCopy( self->s.pos.trBase, tent->s.origin2 ); - fired = qtrue; - } + tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); + tent->s.generic1 = self->s.number; //src + tent->s.clientNum = enemy->s.number; //dest + VectorCopy( self->s.pos.trBase, tent->s.origin2 ); + fired = qtrue; + } - // Actual damage is done by radius - if( fired ) - { - self->timestamp = level.time; - if( self->dcc ) - G_SelectiveRadiusDamage( self->s.pos.trBase, self, - REACTOR_ATTACK_DCC_DAMAGE, - REACTOR_ATTACK_DCC_RANGE, self, - MOD_REACTOR, TEAM_HUMANS ); - else - G_SelectiveRadiusDamage( self->s.pos.trBase, self, - REACTOR_ATTACK_DAMAGE, - REACTOR_ATTACK_RANGE, self, - MOD_REACTOR, TEAM_HUMANS ); - } + // Actual damage is done by radius + if( fired ) + { + self->timestamp = level.time; + if( G_IsDCCBuilt( ) ) + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DCC_DAMAGE, + REACTOR_ATTACK_DCC_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); + else + G_SelectiveRadiusDamage( self->s.pos.trBase, self, + REACTOR_ATTACK_DAMAGE, + REACTOR_ATTACK_RANGE, self, + MOD_REACTOR, TEAM_HUMANS ); } - if( self->dcc ) - self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT; - else - self->nextthink = level.time + REACTOR_ATTACK_REPEAT; + if( G_IsDCCBuilt( ) ) + self->nextthink -= REACTOR_ATTACK_DCC_REPEAT - REACTOR_ATTACK_REPEAT; } //================================================================================== +/* +================ +HArmoury_Think +* +Think function for Human Armoury +================ +*/ +void HArmoury_Think( gentity_t *self ) +{ + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + if( !self->spawned ) + return; + G_CheckPower( self, ARMOURY_CURRENT ); +} /* ================ @@ -1953,57 +2016,9 @@ void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator } } -/* -================ -HArmoury_Think - -Think for armoury -================ -*/ -void HArmoury_Think( gentity_t *self ) -{ - //make sure we have power - self->nextthink = level.time + POWER_REFRESH_TIME; - - self->powered = G_FindPower( self, qfalse ); - - G_SuicideIfNoPower( self ); -} - - - - -//================================================================================== - - - - - -/* -================ -HDCC_Think - -Think for dcc -================ -*/ -void HDCC_Think( gentity_t *self ) -{ - //make sure we have power - self->nextthink = level.time + POWER_REFRESH_TIME; - - self->powered = G_FindPower( self, qfalse ); - - G_SuicideIfNoPower( self ); -} - - - - //================================================================================== - - /* ================ HMedistat_Die @@ -2037,10 +2052,12 @@ void HMedistat_Think( gentity_t *self ) qboolean occupied = qfalse; self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; - - self->powered = G_FindPower( self, qfalse ); - if( G_SuicideIfNoPower( self ) ) + + if( !self->spawned || self->health <= 0 ) return; + + G_CheckPower( self, MEDISTAT_I_IDLE ); + G_IdlePowerState( self ); //clear target's healing flag @@ -2055,105 +2072,105 @@ void HMedistat_Think( gentity_t *self ) self->active = qfalse; self->enemy = NULL; } - - self->nextthink = level.time + POWER_REFRESH_TIME; - return; } - if( self->spawned ) + VectorAdd( self->s.origin, self->r.maxs, maxs ); + VectorAdd( self->s.origin, self->r.mins, mins ); + + mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; + maxs[ 2 ] += 60; //player height + + //if active use the healing idle + if( self->active ) + G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); + + //check if a previous occupier is still here + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) { - VectorAdd( self->s.origin, self->r.maxs, maxs ); - VectorAdd( self->s.origin, self->r.mins, mins ); + player = &g_entities[ entityList[ i ] ]; - mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; - maxs[ 2 ] += 60; //player height + if( player->flags & FL_NOTARGET ) + continue; // notarget cancels even beneficial effects? + + //remove poison from everyone, not just the healed player + if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED ) + player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + if( self->enemy == player && player->client && + ( ( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && + player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && + PM_Live( player->client->ps.pm_type ) ) ) + { + if( !G_Surge( self, MEDISTAT_I_ACTIVE ) ) + return; - //if active use the healing idle - if( self->active ) - G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); - - //check if a previous occupier is still here - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + occupied = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; + } + } + + if( !occupied ) + { + self->enemy = NULL; + + //look for something to heal for( i = 0; i < num; i++ ) { player = &g_entities[ entityList[ i ] ]; if( player->flags & FL_NOTARGET ) continue; // notarget cancels even beneficial effects? - - //remove poison from everyone, not just the healed player - if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED ) - player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; - - if( self->enemy == player && player->client && - ( (player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && // DIFF NOTE: remove this change from diffs ASAP - player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && - PM_Live( player->client->ps.pm_type ) ) ) - { - occupied = qtrue; - player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; - } - } - - if( !occupied ) - { - self->enemy = NULL; - //look for something to heal - for( i = 0; i < num; i++ ) + if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - player = &g_entities[ entityList[ i ] ]; - - if( player->flags & FL_NOTARGET ) - continue; // notarget cancels even beneficial effects? - - if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] || + player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) && + PM_Live( player->client->ps.pm_type ) ) { - if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] || - player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) && - PM_Live( player->client->ps.pm_type ) ) + if( !G_Surge( self, MEDISTAT_I_ACTIVE ) ) + return; + + self->enemy = player; + + //start the heal anim + if( !self->active && self->surge && self->surgePowered ) { - self->enemy = player; - - //start the heal anim - if( !self->active ) - { - G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - self->active = qtrue; - player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; - } + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->active = qtrue; + player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; } - else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) - BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats ); } + else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats ); } } + } - //nothing left to heal so go back to idling - if( !self->enemy && self->active ) - { - G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); - G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); + //nothing left to heal so go back to idling + if( !self->enemy && self->active ) + { + G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); + G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); - self->active = qfalse; - } - else if( self->enemy && self->enemy->client ) //heal! - { - if( self->enemy->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) - self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE; + self->active = qfalse; + } + else if( self->enemy && self->enemy->client ) //heal! + { + if( self->enemy->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE; - if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) - self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) + self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; - self->enemy->health++; + self->enemy->health++; - //if they're completely healed, give them a medkit - if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) - { - self->enemy->health = self->enemy->client->ps.stats[ STAT_MAX_HEALTH ]; - if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) - BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); - } + //if they're completely healed, give them a medkit + if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) + { + self->enemy->health = self->enemy->client->ps.stats[ STAT_MAX_HEALTH ]; + if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); } } } @@ -2179,8 +2196,8 @@ qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, trace_t tr; vec3_t dir, end; - if( !target || target->health <= 0 || !target->client || - target->client->pers.teamSelection != TEAM_ALIENS ) + if( !target || target->health <= 0 || !target->client /*|| + target->client->pers.teamSelection != TEAM_ALIENS */) //debug: target humans too for now return qfalse; if( target->flags & FL_NOTARGET ) @@ -2368,6 +2385,7 @@ HMGTurret_Think Think function for MG turret ================ */ + void HMGTurret_Think( gentity_t *self ) { self->nextthink = level.time + @@ -2375,10 +2393,12 @@ void HMGTurret_Think( gentity_t *self ) // Turn off client side muzzle flashes self->s.eFlags &= ~EF_FIRING; - - self->powered = G_FindPower( self, qfalse ); - if( G_SuicideIfNoPower( self ) ) + + if( !self->spawned || self->health <= 0 ) return; + + G_CheckPower( self, MGTURRET_I_IDLE ); + G_IdlePowerState( self ); // If not powered or spawned don't do anything @@ -2386,10 +2406,9 @@ void HMGTurret_Think( gentity_t *self ) { // if power loss drop turret if( self->spawned && - HMGTurret_State( self, MGT_STATE_INACTIVE ) ) + HMGTurret_State( self, MGT_STATE_INACTIVE ) ); return; - self->nextthink = level.time + POWER_REFRESH_TIME; return; } if( !self->spawned ) @@ -2428,6 +2447,9 @@ void HMGTurret_Think( gentity_t *self ) if( !self->active || self->turretSpinupTime > level.time ) return; + if( !G_Surge( self, MGTURRET_I_ACTIVE ) ) + return; + // Fire repeat delay if( self->timestamp > level.time ) return; @@ -2456,62 +2478,174 @@ Think function for Tesla Generator */ void HTeslaGen_Think( gentity_t *self ) { + vec3_t origin, range, mins, maxs; + int entityList[ MAX_GENTITIES ], i, num; + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; - self->powered = G_FindPower( self, qfalse ); - if( G_SuicideIfNoPower( self ) ) + if( !self->spawned || self->health <= 0 ) return; + + G_CheckPower( self, TESLAGEN_I_IDLE ); + G_IdlePowerState( self ); //if not powered don't do anything and check again for power next think if( !self->powered ) { self->s.eFlags &= ~EF_FIRING; - self->nextthink = level.time + POWER_REFRESH_TIME; return; } - if( self->spawned && self->timestamp < level.time ) - { - vec3_t origin, range, mins, maxs; - int entityList[ MAX_GENTITIES ], i, num; + if( !self->spawned ) + return; - // Communicates firing state to client - self->s.eFlags &= ~EF_FIRING; + // Communicates firing state to client + self->s.eFlags &= ~EF_FIRING; - // Move the muzzle from the entity origin up a bit to fire over turrets - VectorMA( self->s.origin, self->r.maxs[ 2 ], self->s.origin2, origin ); + // Move the muzzle from the entity origin up a bit to fire over turrets + VectorMA( self->s.origin, self->r.maxs[ 2 ], self->s.origin2, origin ); - VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); - VectorAdd( origin, range, maxs ); - VectorSubtract( origin, range, mins ); + VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); - // Attack nearby Aliens - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - self->enemy = &g_entities[ entityList[ i ] ]; + // Attack nearby Aliens + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + self->enemy = &g_entities[ entityList[ i ] ]; - if( self->enemy->flags & FL_NOTARGET ) - continue; + if( self->enemy->flags & FL_NOTARGET ) + continue; + + if( self->enemy->client && self->enemy->health > 0 && + /*self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS &&*/ + Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE ) + { + if( !G_Surge( self, TESLAGEN_I_ACTIVE ) ) + break; - if( self->enemy->client && self->enemy->health > 0 && - self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && - Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE ) - FireWeapon( self ); + FireWeapon( self ); } - self->enemy = NULL; + } + self->enemy = NULL; - if( self->s.eFlags & EF_FIRING ) - { - G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + if( self->s.eFlags & EF_FIRING ) + { + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); - //doesn't really need an anim - //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + //doesn't really need an anim + //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - self->timestamp = level.time + TESLAGEN_REPEAT; - } + self->timestamp = level.time + TESLAGEN_REPEAT; + } +} + +//================================================================================== + +void HDCC_Think( gentity_t *self ) +{ + int i, count, list[ MAX_GENTITIES ]; + gentity_t *ent; + vec3_t mins, maxs; + + self->nextthink = level.time + DC_HEALRATE; + + if( !self->spawned || self->health <= 0 ) + return; + + G_CheckPower( self, DC_I_IDLE ); + G_IdlePowerState( self ); + if( !self->powered ) + return; + + for( i = 0; i < 3; i++ ) + mins[ i ] = self->s.origin[ i ] - DC_RANGE, + maxs[ i ] = self->s.origin[ i ] + DC_RANGE; + + count = trap_EntitiesInBox( mins, maxs, list, MAX_GENTITIES ); + + for( i = 0; i < count; i++ ) + { + ent = g_entities + list[ i ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + if( ent->buildableTeam != TEAM_HUMANS ) + continue; + if( ent->health >= BG_Buildable( ent->s.modelindex, ent->cuboidSize )->health ) + continue; + if( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME >= level.time ) + continue; + if( BG_Buildable( ent->s.modelindex, NULL )->cuboid && + !BG_CuboidAttributes( ent->s.modelindex )->repairable ) + continue; + + if( !G_Surge( self, DC_I_ACTIVE ) ) + return; + + ent->health++; + } +} + +//================================================================================== + +void HCapbank_Think( gentity_t *self) +{ + //nothing to do here +} + +//================================================================================== + +void HRTG_Think( gentity_t *self) +{ + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + if( !self->spawned || self->health <= 0 ) + return; + + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + self->storedBP = 0; + return; + } + + self->storedBP += RTG_YIELD * g_massYieldModifier.value; + + if( self->storedBP > RTG_STORAGE ) + self->storedBP = RTG_STORAGE; +} + +//================================================================================== + +void HRefinery_Think( gentity_t *self) +{ + self->nextthink = level.time + BG_Buildable( self->s.modelindex, NULL )->nextthink; + + if( !self->spawned || self->health <= 0 ) + return; + + G_CheckPower( self, REFINERY_I_IDLE ); + G_IdlePowerState( self ); + + if( G_TimeTilSuddenDeath( ) <= 0 ) + { + self->storedBP = 0; + return; } + + //nothing to do + if( self->storedBP >= REFINERY_STORAGE - 0.001f ) + return; + + if( !G_Surge( self, REFINERY_I_ACTIVE ) ) + return; + + self->storedBP += REFINERY_YIELD * g_massYieldModifier.value; + + if( self->storedBP > REFINERY_STORAGE ) + self->storedBP = REFINERY_STORAGE; } //================================================================================== @@ -2534,7 +2668,6 @@ void Cuboid_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); // just for sound self->die = nullDieFunction; self->killedBy = attacker - g_entities; - self->powered = qfalse; self->s.eFlags &= ~EF_FIRING; G_LogDestruction( self, attacker, mod ); dir[ 0 ] = @@ -2632,48 +2765,6 @@ void G_QueueBuildPoints( gentity_t *self ) level.alienBuildPointQueue += queuePoints; break; - - case TEAM_HUMANS: - powerEntity = G_PowerEntityForEntity( self ); - - if( powerEntity ) - { - int nqt; - switch( powerEntity->s.modelindex ) - { - case BA_H_REACTOR: - nqt = G_NextQueueTime( level.humanBuildPointQueue, - g_humanBuildPoints.integer, - g_humanBuildQueueTime.integer ); - if( !level.humanBuildPointQueue || - level.time + nqt < level.humanNextQueueTime ) - level.humanNextQueueTime = level.time + nqt; - - level.humanBuildPointQueue += queuePoints; - break; - - case BA_H_REPEATER: - if( powerEntity->usesBuildPointZone && - level.buildPointZones[ powerEntity->buildPointZone ].active ) - { - buildPointZone_t *zone = &level.buildPointZones[ powerEntity->buildPointZone ]; - - nqt = G_NextQueueTime( zone->queuedBuildPoints, - zone->totalBuildPoints, - g_humanRepeaterBuildQueueTime.integer ); - - if( !zone->queuedBuildPoints || - level.time + nqt < zone->nextQueueTime ) - zone->nextQueueTime = level.time + nqt; - - zone->queuedBuildPoints += queuePoints; - } - break; - - default: - break; - } - } } } @@ -2774,6 +2865,7 @@ void G_BuildableThink( gentity_t *ent, int msec ) { G_TeamCommand( TEAM_ALIENS, "cp \"The Overmind has awakened!\"" ); } + G_SetupPowerEntity( ent ); } // Timer actions @@ -2784,9 +2876,18 @@ void G_BuildableThink( gentity_t *ent, int msec ) if( !ent->spawned ) { - buildRate=(int)(ceil((float)maxHealth/(float)buildTime*1e3f)); - ent->health=MIN(ent->health+buildRate,maxHealth); - ent->healthLeft=MAX(ent->healthLeft-buildRate,0); + buildRate = (int)( ceil( (float)maxHealth / (float)buildTime * 1.0e3f ) ); + + if( ent->buildableTeam == TEAM_HUMANS && + ent->s.modelindex != BA_H_RTG ) + { + buildRate *= ent->current / PREBUILD_CURRENT; + if( buildRate < 0 ) + buildRate = 0; + ent->powered = ( buildRate > 0 ); + } + ent->health = MIN( ent->health + buildRate, maxHealth ); + ent->healthLeft = MAX( ent->healthLeft - buildRate, 0 ); } else if( ent->health > 0 && ent->health < maxHealth ) { @@ -2795,13 +2896,6 @@ void G_BuildableThink( gentity_t *ent, int msec ) { ent->health += regenRate; } - else if( ent->buildableTeam == TEAM_HUMANS && ent->dcc && - ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time && - ( !BG_Buildable( ent->s.modelindex, NULL )->cuboid || - BG_CuboidAttributes( ent->s.modelindex )->repairable ) ) - { - ent->health += DC_HEALRATE * ent->dcc; - } } if( ent->health >= maxHealth ) @@ -2819,8 +2913,6 @@ void G_BuildableThink( gentity_t *ent, int msec ) if( ent->clientSpawnTime < 0 ) ent->clientSpawnTime = 0; - ent->dcc = ( ent->buildableTeam != TEAM_HUMANS ) ? 0 : G_FindDCC( ent ); - // Set health ent->s.generic1 = MIN( MAX( ent->health, 0 ), 999 ); @@ -2828,16 +2920,47 @@ void G_BuildableThink( gentity_t *ent, int msec ) BG_CuboidPackHealthSafe( ent->s.modelindex, &ent->s, ent->health ); // Set flags - ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_MARKED ); - if( ent->powered ) - ent->s.eFlags |= EF_B_POWERED; + ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_SURGE ); + + if( ent->buildableTeam == TEAM_HUMANS ) + { + if( ent->s.modelindex == BA_H_REPEATER ) + { + if( ent->powered ) + ent->s.eFlags |= EF_B_POWERED; + if( ent->active ) + ent->s.eFlags |= EF_B_SURGE; + } + else if( ent->isPowerSource ) + { + ent->s.eFlags |= EF_B_POWERED; + if( ent->active ) + ent->s.eFlags |= EF_B_SURGE; + } + else + { + if( ent->powered ) + { + if( ent->surge ) + { + ent->s.eFlags |= EF_B_SURGE; + if( ent->surgePowered ) + ent->s.eFlags |= EF_B_POWERED; + } + else + ent->s.eFlags |= EF_B_POWERED; + } + } + } + else + { + if( ent->powered ) + ent->s.eFlags |= EF_B_POWERED; + } if( ent->spawned ) ent->s.eFlags |= EF_B_SPAWNED; - if( ent->deconstruct ) - ent->s.eFlags |= EF_B_MARKED; - // Check if this buildable is touching any triggers G_BuildableTouchTriggers( ent ); @@ -2938,201 +3061,6 @@ static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA, vec3_t cub return BoundsIntersect( minsA, maxsA, minsB, maxsB ); } -/* -=============== -G_CompareBuildablesForRemoval - -qsort comparison function for a buildable removal list -=============== -*/ -static buildable_t cmpBuildable; -static vec3_t cmpOrigin; -static vec3_t cmpCuboid; -static int G_CompareBuildablesForRemoval( const void *a, const void *b ) -{ - int precedence[ ] = - { - BA_NONE, - - BA_A_BARRICADE, - BA_A_ACIDTUBE, - BA_A_TRAPPER, - BA_A_HIVE, - BA_A_BOOSTER, - BA_A_SPAWN, - BA_A_CUBOID1, - BA_A_OVERMIND, - - BA_H_MGTURRET, - BA_H_TESLAGEN, - BA_H_DCC, - BA_H_MEDISTAT, - BA_H_ARMOURY, - BA_H_SPAWN, - BA_H_REPEATER, - BA_H_CUBOID1, - BA_H_CUBOID2, - BA_H_REACTOR - }; - - gentity_t *buildableA, *buildableB; - int i; - int aPrecedence = 0, bPrecedence = 0; - qboolean aMatches = qfalse, bMatches = qfalse; - - buildableA = *(gentity_t **)a; - buildableB = *(gentity_t **)b; - - // Prefer the one that collides with the thing we're building - aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, cmpCuboid, - buildableA->s.modelindex, buildableA->s.origin, buildableA->cuboidSize ); - bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, cmpCuboid, - buildableB->s.modelindex, buildableB->s.origin, buildableB->cuboidSize ); - if( aMatches && !bMatches ) - return -1; - else if( !aMatches && bMatches ) - return 1; - - // If the only spawn is marked, prefer it last - if( cmpBuildable == BA_A_SPAWN || cmpBuildable == BA_H_SPAWN ) - { - if( ( buildableA->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || - ( buildableA->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) - return 1; - - if( ( buildableB->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || - ( buildableB->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) - return -1; - } - - // If one matches the thing we're building, prefer it - aMatches = ( buildableA->s.modelindex == cmpBuildable ); - bMatches = ( buildableB->s.modelindex == cmpBuildable ); - if( aMatches && !bMatches ) - return -1; - else if( !aMatches && bMatches ) - return 1; - - // They're the same type - if( buildableA->s.modelindex == buildableB->s.modelindex ) - { - gentity_t *powerEntity = G_PowerEntityForPoint( cmpOrigin ); - - // Prefer the entity that is providing power for this point - aMatches = ( powerEntity == buildableA ); - bMatches = ( powerEntity == buildableB ); - if( aMatches && !bMatches ) - return -1; - else if( !aMatches && bMatches ) - return 1; - - // Pick the one marked earliest - return buildableA->deconstructTime - buildableB->deconstructTime; - } - - // Resort to preference list - for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) - { - if( buildableA->s.modelindex == precedence[ i ] ) - aPrecedence = i; - - if( buildableB->s.modelindex == precedence[ i ] ) - bPrecedence = i; - } - - return aPrecedence - bPrecedence; -} - -/* -=============== -G_ClearDeconMarks - -Remove decon mark from all buildables -=============== -*/ -void G_ClearDeconMarks( void ) -{ - int i; - gentity_t *ent; - - for( i = MAX_CLIENTS, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) - { - if( !ent->inuse ) - continue; - - if( ent->s.eType != ET_BUILDABLE ) - continue; - - ent->deconstruct = qfalse; - } -} - -/* -=============== -G_FreeMarkedBuildables - -Free up build points for a team by deconstructing marked buildables -=============== -*/ -void G_FreeMarkedBuildables( gentity_t *deconner, char *readable, int rsize, - char *nums, int nsize ) -{ - int i; - int bNum; - int listItems = 0; - int totalListItems = 0; - gentity_t *ent; - int removalCounts[ BA_NUM_BUILDABLES ] = {0}; - - if( readable && rsize ) - readable[ 0 ] = '\0'; - if( nums && nsize ) - nums[ 0 ] = '\0'; - - if( !g_markDeconstruct.integer ) - return; // Not enabled, can't deconstruct anything - - for( i = 0; i < level.numBuildablesForRemoval; i++ ) - { - ent = level.markedBuildables[ i ]; - bNum = BG_Buildable( ent->s.modelindex, NULL )->number; - - if( removalCounts[ bNum ] == 0 ) - totalListItems++; - - G_Damage( ent, NULL, deconner, NULL, NULL, ent->health, 0, MOD_REPLACE ); - - removalCounts[ bNum ]++; - - if( nums ) - Q_strcat( nums, nsize, va( " %d", ent - g_entities ) ); - - G_FreeEntity( ent ); - } - - if( !readable ) - return; - - for( i = 0; i < BA_NUM_BUILDABLES; i++ ) - { - if( removalCounts[ i ] ) - { - if( listItems ) - { - if( listItems == ( totalListItems - 1 ) ) - Q_strcat( readable, rsize, va( "%s and ", - ( totalListItems > 2 ) ? "," : "" ) ); - else - Q_strcat( readable, rsize, ", " ); - } - Q_strcat( readable, rsize, va( "%s", BG_Buildable( i, NULL )->humanName ) ); - if( removalCounts[ i ] > 1 ) - Q_strcat( readable, rsize, va( " (%dx)", removalCounts[ i ] ) ); - listItems++; - } - } -} - /* =============== G_SufficientBPAvailable @@ -3141,249 +3069,55 @@ Determine if enough build points can be released for the buildable and list the buildables that must be destroyed if this is the case =============== */ -static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, +static itemBuildError_t G_SufficientBPAvailable( gclient_t *builder, + buildable_t buildable, vec3_t origin, - vec3_t cuboidSize ) + vec3_t cuboidSize ) { - int i; - int numBuildables = 0; - int numRequired = 0; - int pointsYielded = 0; - gentity_t *ent; - team_t team = BG_Buildable( buildable, NULL )->team; - int buildPoints = BG_Buildable( buildable, cuboidSize )->buildPoints; - int remainingBP, remainingSpawns; - qboolean collision = qfalse; - int collisionCount = 0; - qboolean repeaterInRange = qfalse; - int repeaterInRangeCount = 0; - itemBuildError_t bpError; - buildable_t spawn; - buildable_t core; - int spawnCount = 0; - qboolean changed = qtrue; - - level.numBuildablesForRemoval = 0; - - if( team == TEAM_ALIENS ) - { - remainingBP = G_GetBuildPoints( origin, team ); - remainingSpawns = level.numAlienSpawns; - bpError = IBE_NOALIENBP; - spawn = BA_A_SPAWN; - core = BA_A_OVERMIND; - } - else if( team == TEAM_HUMANS ) - { - if( buildable == BA_H_REACTOR || buildable == BA_H_REPEATER ) - remainingBP = level.humanBuildPoints; - else - remainingBP = G_GetBuildPoints( origin, team ); - - remainingSpawns = level.numHumanSpawns; - bpError = IBE_NOHUMANBP; - spawn = BA_H_SPAWN; - core = BA_H_REACTOR; - } - else - { - Com_Error( ERR_FATAL, "team is %d\n", team ); - return IBE_NONE; - } - - // Simple non-marking case - if( !g_markDeconstruct.integer ) + int required; + int available; + int error; + + required = BG_Buildable( buildable, cuboidSize )->buildPoints; + + switch( BG_Buildable( buildable, NULL )->team ) { - if( remainingBP - buildPoints < 0 ) - return bpError; - - // Check for buildable<->buildable collisions - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) - { - if( ent->s.eType != ET_BUILDABLE ) - continue; - - if( G_BuildablesIntersect( buildable, origin, cuboidSize, ent->s.modelindex, ent->s.origin, ent->cuboidSize ) ) - return IBE_NOROOM; - - } - - return IBE_NONE; + case TEAM_ALIENS: + error = IBE_NOALIENBP; + available = G_GetBuildPoints( origin, TEAM_ALIENS ); + break; + + case TEAM_HUMANS: + error = IBE_NOHUMANBP; + if( !builder ) + available = required; //always enough + else + //first RTG is free + if( buildable == BA_H_RTG && !G_IsRTGBuilt() ) + available = INFINITE; + else + available = builder->ps.persistant[ PERS_BUILDPOINTS ]; + break; } + + if( required > available ) + return error; + + return IBE_NONE; - // Set buildPoints to the number extra that are required - buildPoints -= remainingBP; - // Build a list of buildable entities +#if 0 + // Check for buildable<->buildable collisions + // FIXME: is this check even needed? for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) { if( ent->s.eType != ET_BUILDABLE ) continue; - collision = G_BuildablesIntersect( buildable, origin, cuboidSize, ent->s.modelindex, ent->s.origin, ent->cuboidSize ); - - if( collision ) - { - // Don't allow replacements at all - if( g_markDeconstruct.integer == 1 ) - return IBE_NOROOM; - - // Only allow replacements of the same type - if( g_markDeconstruct.integer == 2 && ent->s.modelindex != buildable ) - return IBE_NOROOM; - - // Any other setting means anything goes - - collisionCount++; - } - - // Check if this is a repeater and it's in range - if( buildable == BA_H_REPEATER && - buildable == ent->s.modelindex && - Distance( ent->s.origin, origin ) < REPEATER_BASESIZE ) - { - repeaterInRange = qtrue; - repeaterInRangeCount++; - } - else - repeaterInRange = qfalse; - - // Don't allow marked buildables to be replaced in another zone, - // unless the marked buildable isn't in a zone (and thus unpowered) - if( team == TEAM_HUMANS && - buildable != BA_H_REACTOR && - buildable != BA_H_REPEATER && - ent->parentNode != G_PowerEntityForPoint( origin ) ) - continue; - - if( !ent->inuse ) - continue; - - if( ent->health <= 0 ) - continue; - - if( ent->buildableTeam != team ) - continue; - - // Explicitly disallow replacement of the core buildable with anything - // other than the core buildable - if( ent->s.modelindex == core && buildable != core ) - continue; - - // Don't allow a power source to be replaced by a dependant - if( team == TEAM_HUMANS && - G_PowerEntityForPoint( origin ) == ent && - buildable != BA_H_REPEATER && - buildable != core ) - continue; - - // Don't include unpowered buildables - if( !collision && !ent->powered ) - continue; - - if( ent->deconstruct ) - { - level.markedBuildables[ numBuildables++ ] = ent; - - // Buildables that are marked here will always end up at the front of the - // removal list, so just incrementing numBuildablesForRemoval is sufficient - if( collision || repeaterInRange ) - { - // Collided with something, so we definitely have to remove it or - // it's a repeater that intersects the new repeater's power area, - // so it must be removed - - if( collision ) - collisionCount--; - - if( repeaterInRange ) - repeaterInRangeCount--; - - if( ent->powered ) - pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; - level.numBuildablesForRemoval++; - } - else if( BG_Buildable( ent->s.modelindex, NULL )->uniqueTest && - ent->s.modelindex == buildable ) - { - // If it's a unique buildable, it must be replaced by the same type - if( ent->powered ) - pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; - level.numBuildablesForRemoval++; - } - } - } - - numRequired = level.numBuildablesForRemoval; - - // We still need build points, but have no candidates for removal - if( buildPoints > 0 && numBuildables == 0 ) - return bpError; - - // Collided with something we can't remove - if( collisionCount > 0 ) - return IBE_NOROOM; - - // There are one or more repeaters we can't remove - if( repeaterInRangeCount > 0 ) - return IBE_RPTPOWERHERE; - - // Sort the list - cmpBuildable = buildable; - VectorCopy( origin, cmpOrigin ); - VectorCopy( cuboidSize, cmpCuboid ); - qsort( level.markedBuildables, numBuildables, sizeof( level.markedBuildables[ 0 ] ), - G_CompareBuildablesForRemoval ); - - // Determine if there are enough markees to yield the required BP - for( ; pointsYielded < buildPoints && level.numBuildablesForRemoval < numBuildables; - level.numBuildablesForRemoval++ ) - { - ent = level.markedBuildables[ level.numBuildablesForRemoval ]; - if( ent->powered ) - pointsYielded += BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; - } - - // Do another pass to see if we can meet quota with fewer buildables - // than we have now due to mismatches between priority and BP amounts - // by repeatedly testing if we can chop off the first thing that isn't - // required by rules of collision/uniqueness, which are always at the head - while( changed && level.numBuildablesForRemoval > 1 && - level.numBuildablesForRemoval > numRequired ) - { - int pointsUnYielded = 0; - changed = qfalse; - ent = level.markedBuildables[ numRequired ]; - if( ent->powered ) - pointsUnYielded = BG_Buildable( ent->s.modelindex, ent->cuboidSize )->buildPoints; - - if( pointsYielded - pointsUnYielded >= buildPoints ) - { - pointsYielded -= pointsUnYielded; - memmove( &level.markedBuildables[ numRequired ], - &level.markedBuildables[ numRequired + 1 ], - ( level.numBuildablesForRemoval - numRequired ) - * sizeof( gentity_t * ) ); - level.numBuildablesForRemoval--; - changed = qtrue; - } - } - - for( i = 0; i < level.numBuildablesForRemoval; i++ ) - { - if( level.markedBuildables[ i ]->s.modelindex == spawn ) - spawnCount++; + if( G_BuildablesIntersect( buildable, origin, cuboidSize, ent->s.modelindex, ent->s.origin, ent->cuboidSize ) ) + return IBE_NOROOM; } - - // Make sure we're not removing the last spawn - if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 ) - return IBE_LASTSPAWN; - - // Not enough points yielded - if( pointsYielded < buildPoints ) - return bpError; - else - return IBE_NONE; +#endif } /* @@ -3410,23 +3144,6 @@ static void G_SetBuildableLinkState( qboolean link ) } } -static void G_SetBuildableMarkedLinkState( qboolean link ) -{ - int i; - gentity_t *ent; - - for( i = 0; i < level.numBuildablesForRemoval; i++ ) - { - ent = level.markedBuildables[ i ]; - if( link ) - trap_LinkEntity( ent ); - else - trap_UnlinkEntity( ent ); - } -} - - - /* ================ G_CanBuild @@ -3482,7 +3199,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance contents = trap_PointContents( entity_origin, -1 ); - if( ( tempReason = G_SufficientBPAvailable( buildable, origin, cuboidSize ) ) != IBE_NONE ) + if( ( tempReason = G_SufficientBPAvailable( ent->client, buildable, origin, cuboidSize ) ) != IBE_NONE ) reason = tempReason; if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) @@ -3510,32 +3227,14 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance else if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { //human criteria - - // Check for power - if( G_IsPowered( entity_origin ) == BA_NONE ) - { - //tell player to build a repeater to provide power - if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) - reason = IBE_NOPOWERHERE; - } + if( !G_PowerForPoint( origin ) && + buildable != BA_H_RTG ) + reason = IBE_NOPOWERHERE; //this buildable requires a DCC if( BG_Buildable( buildable, NULL )->dccTest && !G_IsDCCBuilt( ) ) reason = IBE_NODCC; - //check that there is a parent reactor when building a repeater - if( buildable == BA_H_REPEATER ) - { - tempent = G_Reactor( ); - - if( tempent == NULL ) // No reactor - reason = IBE_RPTNOREAC; - else if( g_markDeconstruct.integer && G_IsPowered( entity_origin ) == BA_H_REACTOR ) - reason = IBE_RPTPOWERHERE; - else if( !g_markDeconstruct.integer && G_IsPowered( entity_origin ) ) - reason = IBE_RPTPOWERHERE; - } - // Check permission to build here if( tr1.surfaceFlags & SURF_NOHUMANBUILD || contents & CONTENTS_NOHUMANBUILD ) reason = IBE_PERMISSION; @@ -3549,7 +3248,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( BG_Buildable( buildable, NULL )->uniqueTest ) { tempent = G_FindBuildable( buildable ); - if( tempent && !tempent->deconstruct ) + if( tempent ) { switch( buildable ) { @@ -3570,18 +3269,12 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance //check there is enough room to spawn from (presuming this is a spawn) if( reason == IBE_NONE ) - { - G_SetBuildableMarkedLinkState( qfalse ); if( G_CheckSpawnPoint( ENTITYNUM_NONE, origin, normal, buildable, NULL ) != NULL ) reason = IBE_NORMAL; - G_SetBuildableMarkedLinkState( qtrue ); - } //this item does not fit here if( reason == IBE_NONE && ( tr2.startsolid || !COMPARE_FLOAT_EPSILON(tr3.fraction,1.0f) ) ) reason = IBE_NOROOM; - if( reason != IBE_NONE ) - level.numBuildablesForRemoval = 0; if( g_buildableDensityLimit.integer > 0 ) { @@ -3661,12 +3354,11 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, { gentity_t *built; vec3_t localOrigin; - char readable[ MAX_STRING_CHARS ]; char buildnums[ MAX_STRING_CHARS ]; buildLog_t *log; qboolean cuboid; - cuboid=BG_Buildable(buildable,NULL)->cuboid; + cuboid = BG_Buildable(buildable,NULL)->cuboid; VectorCopy( origin, localOrigin ); @@ -3675,10 +3367,6 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, else log = NULL; - // Free existing buildables - G_FreeMarkedBuildables( builder, readable, sizeof( readable ), - buildnums, sizeof( buildnums ) ); - // Spawn the buildable built = G_Spawn(); built->s.eType = ET_BUILDABLE; @@ -3692,13 +3380,13 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, if( !builder->client ) VectorMA( localOrigin, 1.0f, normal, localOrigin ); - if(cuboid) + if( cuboid ) { - BG_CuboidBBox(cuboidSize,built->r.mins,built->r.maxs); - VectorCopy(cuboidSize,&built->cuboidSize); + BG_CuboidBBox( cuboidSize, built->r.mins, built->r.maxs ); + VectorCopy( cuboidSize, &built->cuboidSize ); } else - BG_BuildableBoundingBox(buildable,built->r.mins,built->r.maxs); + BG_BuildableBoundingBox( buildable, built->r.mins, built->r.maxs ); built->health = 1; built->healthLeft = BG_Buildable( buildable, cuboidSize )->health-1; @@ -3713,8 +3401,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, built->spawned = qfalse; built->buildTime = built->s.time = level.time; - // build instantly in cheat mode - if( builder->client && (g_cheats.integer || g_instantBuild.integer) ) + if( builder->client && g_instantBuild.integer ) { built->health = BG_Buildable( buildable, cuboidSize )->health; built->buildTime = built->s.time = @@ -3804,16 +3491,34 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, case BA_H_REACTOR: built->think = HReactor_Think; built->die = HSpawn_Die; - built->use = HRepeater_Use; + built->use = HSwitchable_Use; built->powered = built->active = qtrue; break; case BA_H_REPEATER: built->think = HRepeater_Think; built->die = HRepeater_Die; - built->use = HRepeater_Use; + built->use = HSwitchable_Use; built->count = -1; break; + + case BA_H_CAPBANK: + built->think = HCapbank_Think; + built->die = HSpawn_Die; + built->use = HSwitchable_Use; + break; + + case BA_H_RTG: + built->think = HRTG_Think; + built->die = HSpawn_Die; + built->use = HSwitchable_Use; + break; + + case BA_H_REFINERY: + built->think = HRefinery_Think; + built->die = HSpawn_Die; + //built->use = HSwitchable_Use; + break; default: //erk @@ -3839,15 +3544,14 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, G_SetOrigin( built, localOrigin ); - // roughly nudge the buildable onto the surface D:< VectorScale( normal, -512.0f, built->s.pos.trDelta ); - if(BG_Buildable(buildable, NULL)->cuboid) - VectorCopy(cuboidSize,built->s.angles); + if( BG_Buildable( buildable, NULL )->cuboid ) + VectorCopy( cuboidSize,built->s.angles ); else { - VectorCopy( angles, built->s.angles ); - built->s.angles[ PITCH ] = 0.0f; + VectorCopy( angles, built->s.angles ); + built->s.angles[ PITCH ] = 0.0f; } built->s.pos.trType = BG_Buildable( buildable, NULL )->traj; @@ -3859,13 +3563,8 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, built->s.angles2[ YAW ] = angles[ YAW ]; built->s.angles2[ PITCH ] = MGTURRET_VERTICALCAP; - if( BG_Buildable( buildable, NULL )->team == TEAM_ALIENS ) - { - built->powered = qtrue; - built->s.eFlags |= EF_B_POWERED; - } - else if( ( built->powered = G_FindPower( built, qfalse ) ) ) - built->s.eFlags |= EF_B_POWERED; + built->powered = qtrue; + built->s.eFlags |= EF_B_POWERED; built->s.eFlags &= ~EF_B_SPAWNED; @@ -3878,32 +3577,47 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, if( built->builtBy >= 0 ) G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); + if( BG_Buildable( buildable, NULL )->team == TEAM_HUMANS ) + { + //special case for the RTG unit that can be built w/o power + if( buildable == BA_H_RTG ) + built->requiresPower = qfalse; + else + built->requiresPower = qtrue; + + built->isPowerSource = qfalse; + built->resistance = PREBUILD_RESISTANCE; + if( BG_Buildable( buildable, NULL )->isPowerSource || + buildable == BA_H_REPEATER ) + built->active = qtrue; //spawn enabled + } + + // subtract build points + if( buildable != BA_H_RTG || G_IsRTGBuilt( ) ) //first RTG is free + if( builder && builder->client ) + builder->client->ps.persistant[ PERS_BUILDPOINTS ] -= BG_Buildable( buildable, cuboidSize )->buildPoints; + + BG_CuboidPackHealthSafe( built->s.modelindex, &built->s, built->health ); + trap_LinkEntity( built ); if( builder && builder->client ) { G_TeamCommand( builder->client->ps.stats[ STAT_TEAM ], - va( "print \"%s ^2built^7 by %s%s%s\n\"", - G_CuboidName(built->s.modelindex,cuboidSize,qfalse), - builder->client->pers.netname, - ( readable[ 0 ] ) ? "^7, ^3replacing^7 " : "", - readable ) ); - G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building " - "%s%s%s\n", + va( "print \"%s ^2built^7 by %s\n\"", + G_CuboidName( built->s.modelindex, cuboidSize, qfalse ), + builder->client->pers.netname ) ); + G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building %s\n", builder - g_entities, built - g_entities, BG_Buildable( built->s.modelindex, NULL )->name, buildnums, builder->client->pers.netname, - G_CuboidName(built->s.modelindex,built->cuboidSize,qtrue), - readable[ 0 ] ? ", replacing " : "", - readable ); + G_CuboidName( built->s.modelindex, built->cuboidSize, qtrue ) ); } if( log ) G_BuildLogSet( log, built ); - - BG_CuboidPackHealthSafe(built->s.modelindex,&built->s,built->health); return built; } @@ -4026,6 +3740,8 @@ static gentity_t *G_FinishSpawningBuildable( gentity_t *ent, qboolean force ) built->health = BG_Buildable( buildable, built->cuboidSize )->health; built->s.eFlags |= EF_B_SPAWNED; + G_SetupPowerEntity( built ); + // drop towards normal surface VectorScale( built->s.origin2, -4096.0f, dest ); VectorAdd( dest, built->s.origin, dest ); @@ -4407,8 +4123,6 @@ buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ) void G_BuildLogSet( buildLog_t *log, gentity_t *ent ) { log->modelindex = ent->s.modelindex; - log->deconstruct = log->deconstruct; - log->deconstructTime = ent->deconstructTime; VectorCopy( ent->s.pos.trBase, log->origin ); VectorCopy( ent->s.angles, log->angles ); VectorCopy( ent->s.origin2, log->origin2 ); @@ -4464,8 +4178,6 @@ void G_BuildLogRevertThink( gentity_t *ent ) } built = G_FinishSpawningBuildable( ent, qtrue ); - if( ( built->deconstruct = ent->deconstruct ) ) - built->deconstructTime = ent->deconstructTime; built->buildTime = built->s.time = 0; G_KillBox( built ); @@ -4482,8 +4194,6 @@ void G_BuildLogRevert( int id ) int i; vec3_t dist; - level.numBuildablesForRemoval = 0; - level.numBuildLogs -= level.buildId - id; while( level.buildId > id ) { @@ -4521,8 +4231,6 @@ void G_BuildLogRevert( int id ) VectorCopy( log->origin2, builder->s.origin2 ); VectorCopy( log->angles2, builder->s.angles2 ); builder->s.modelindex = log->modelindex; - builder->deconstruct = log->deconstruct; - builder->deconstructTime = log->deconstructTime; builder->think = G_BuildLogRevertThink; builder->nextthink = level.time + FRAMETIME; @@ -4539,27 +4247,6 @@ void G_BuildLogRevert( int id ) level.alienBuildPointQueue = MAX( 0, level.alienBuildPointQueue - value ); } - else - { - if( log->powerSource == BA_H_REACTOR ) - { - level.humanBuildPointQueue = - MAX( 0, level.humanBuildPointQueue - value ); - } - else if( log->powerSource == BA_H_REPEATER ) - { - gentity_t *source; - buildPointZone_t *zone; - - source = G_PowerEntityForPoint( log->origin ); - if( source && source->usesBuildPointZone ) - { - zone = &level.buildPointZones[ source->buildPointZone ]; - zone->queuedBuildPoints = - MAX( 0, zone->queuedBuildPoints - value ); - } - } - } } } } diff --git a/src/game/g_client.c b/src/game/g_client.c index 7596ea1..a3c7a67 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1482,6 +1482,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; + persistant[ PERS_BUILDPOINTS ] = 0; // clear buildpoints eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); @@ -1795,11 +1796,12 @@ void ClientDisconnect( int clientNum ) G_RelayCuboidToSpectators Called everytime a player changes his cuboid size. -A server command is issued to everyone spectating him -so that their clients can know the cuboid size as well. ============ */ -void G_RelayCuboidToSpectators(gentity_t *self) +void G_RelayCuboidToSpectators( gentity_t *self ) { + self->client->ps.misc[ MISC_CUBOID_X ] = self->client->cuboidSelection[ 0 ] * 10; + self->client->ps.misc[ MISC_CUBOID_Y ] = self->client->cuboidSelection[ 1 ] * 10; + self->client->ps.misc[ MISC_CUBOID_Z ] = self->client->cuboidSelection[ 2 ] * 10; } diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 2c15638..c1da4e0 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1363,7 +1363,7 @@ void Cmd_CallVote_f( gentity_t *ent ) } else if( !Q_stricmp( vote, "map_restart" ) ) { - if( level.time / 60000 >= g_restartVoteTimelimit.integer ) + if( g_restartVoteTimelimit.integer && ( level.time - level.startTime ) / 60000 >= g_restartVoteTimelimit.integer ) { trap_SendServerCommand( ent-g_entities, va( "print \"%s: It's not allowed to call a restart vote after %i minute%s.\n\"", @@ -1377,7 +1377,7 @@ void Cmd_CallVote_f( gentity_t *ent ) } else if( !Q_stricmp( vote, "map" ) ) { - if( level.time / 60000 >= g_mapVoteTimelimit.integer ) + if( g_mapVoteTimelimit.integer && ( level.time - level.startTime ) / 60000 >= g_mapVoteTimelimit.integer ) { trap_SendServerCommand( ent-g_entities, va( "print \"%s: It's not allowed to call a map vote after %i minute%s. Call a ^1nextmap^7 vote instead\n\"", @@ -1434,7 +1434,7 @@ void Cmd_CallVote_f( gentity_t *ent ) } else if( !Q_stricmp( vote, "sudden_death" ) ) { - if(!g_suddenDeathVotePercent.integer) + if( !g_suddenDeathVotePercent.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Sudden Death votes have been disabled\n\"" ); @@ -1918,6 +1918,7 @@ void Cmd_Destroy_f( gentity_t *ent ) char cmd[ 12 ]; qboolean deconstruct = qtrue; qboolean lastSpawn = qfalse; + int bp; if( ent->client->pers.namelog->denyBuild ) { @@ -1942,22 +1943,21 @@ void Cmd_Destroy_f( gentity_t *ent ) ( ( ent->client->ps.weapon >= WP_ABUILD ) && ( ent->client->ps.weapon <= WP_HBUILD ) ) ) { + //give some BP back: 20% for a dead buildable, 80% for a new one + bp = BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildPoints; + bp *= MAX( (float)traceEnt->health, 0.0f ) / + BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->health * 0.6f + 0.2f; + // Always let the builder prevent the explosion if( traceEnt->health <= 0 ) { + ent->client->ps.persistant[ PERS_BUILDPOINTS ] += bp; G_QueueBuildPoints( traceEnt ); G_RewardAttackers( traceEnt ); G_FreeEntity( traceEnt ); return; } - // Cancel deconstruction (unmark) - if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct ) - { - traceEnt->deconstruct = qfalse; - return; - } - // Prevent destruction of the last spawn if( ent->client->pers.teamSelection == TEAM_ALIENS && traceEnt->s.modelindex == BA_A_SPAWN ) @@ -1972,8 +1972,7 @@ void Cmd_Destroy_f( gentity_t *ent ) lastSpawn = qtrue; } - if( lastSpawn && !g_cheats.integer && - !g_markDeconstruct.integer ) + if( lastSpawn && !g_cheats.integer ) { G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); return; @@ -1986,9 +1985,7 @@ void Cmd_Destroy_f( gentity_t *ent ) return; } - if( !g_markDeconstruct.integer || - ( ent->client->pers.teamSelection == TEAM_HUMANS && - !G_FindPower( traceEnt, qtrue ) ) ) + if( ent->client->pers.teamSelection == TEAM_HUMANS ) { if( ent->client->buildTimer ) { @@ -2004,21 +2001,18 @@ void Cmd_Destroy_f( gentity_t *ent ) G_Damage( traceEnt, ent, ent, forward, tr.endpos, traceEnt->health, 0, MOD_SUICIDE ); } - else if( g_markDeconstruct.integer && - ( ent->client->pers.teamSelection != TEAM_HUMANS || - G_FindPower( traceEnt , qtrue ) || lastSpawn ) ) - { - traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction - traceEnt->deconstructTime = level.time; - } else { if( !g_cheats.integer && !g_instantBuild.integer ) // add a bit to the build timer { - ent->client->buildTimer += - BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildTime / 4; - G_RecalcBuildTimer(ent->client); + ent->client->buildTimer += BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildTime / 4; + G_RecalcBuildTimer(ent->client); } + + ent->client->ps.persistant[ PERS_BUILDPOINTS ] += bp; + if( ent->client->ps.persistant[ PERS_BUILDPOINTS ] > CKIT_STORAGE ) + ent->client->ps.persistant[ PERS_BUILDPOINTS ] = CKIT_STORAGE; + G_Damage( traceEnt, ent, ent, forward, tr.endpos, traceEnt->health, 0, MOD_DECONSTRUCT ); G_FreeEntity( traceEnt ); @@ -2782,6 +2776,69 @@ void Cmd_Reload_f( gentity_t *ent ) playerState_t *ps = &ent->client->ps; int ammo; + // reload transfers mass in case of CKit + if( ps->weapon == WP_HBUILD ) + { + trace_t trace; + vec3_t eyes, view, point; + gentity_t *other; + + #define USE_OBJECT_RANGE 100 + + // look for object infront of player + AngleVectors( ent->client->ps.viewangles, view, NULL, NULL ); + BG_GetClientViewOrigin( &ent->client->ps, eyes ); + VectorMA( eyes, USE_OBJECT_RANGE, view, point ); + + trap_Trace( &trace, ent->client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); + other = &g_entities[ trace.entityNum ]; + + // transfer FROM buildable + if( other->s.eType == ET_BUILDABLE ) + { + if( other->buildableTeam == TEAM_HUMANS && + BG_Buildable( other->s.modelindex, NULL )->hasStorage && + other->spawned && other->health >= 0 && + other->storedBP > 0 ) + { + float bp; + + bp = floor( MIN( MIN( other->storedBP, 4 ), + CKIT_STORAGE - ent->client->ps.persistant[ PERS_BUILDPOINTS ] ) ); + + other->storedBP -= bp; + ent->client->ps.persistant[ PERS_BUILDPOINTS ] += bp; + + if( bp ) + G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); + else + G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); + } + } + //transfer TO another player + else if( other->client ) + { + if( BG_GetPlayerWeapon( &other->client->ps ) == WP_HBUILD && + other->client->ps.stats[ STAT_HEALTH ] >= 0 ) + { + int bp; + + bp = MIN( MIN( ent->client->ps.persistant[ PERS_BUILDPOINTS ], 4 ), + CKIT_STORAGE - other->client->ps.persistant[ PERS_BUILDPOINTS ] ); + + ent->client->ps.persistant[ PERS_BUILDPOINTS ] -= bp; + other->client->ps.persistant[ PERS_BUILDPOINTS ] += bp; + + if( bp ) + G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); + else + G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); + } + } + + return; + } + // weapon doesn't ever need reloading if( BG_Weapon( ps->weapon )->infiniteAmmo ) return; @@ -3305,7 +3362,6 @@ Cmd_Debug1_f */ void Cmd_Debug1_f( gentity_t *other ) { - other->client->ps.stats[ STAT_SHAKE ] += 70; } /* diff --git a/src/game/g_combat.c b/src/game/g_combat.c index fe5d607..6acf6b7 100644 --- a/src/game/g_combat.c +++ b/src/game/g_combat.c @@ -195,17 +195,18 @@ float G_RewardAttackers( gentity_t *self ) AddScore( player, stageValue ); - // killing buildables earns score, but not credits - if( self->s.eType != ET_BUILDABLE ) - { - G_AddCreditToClient( player->client, stageValue, qtrue ); + if( self->s.eType == ET_BUILDABLE ) + stageValue *= g_buildableValueModifier.value; + else + stageValue *= g_playerValueModifier.value; + + G_AddCreditToClient( player->client, stageValue, qtrue ); - // add to stage counters - if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - alienCredits += stageValue; - else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - humanCredits += stageValue; - } + // add to stage counters + if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) + alienCredits += stageValue; + else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) + humanCredits += stageValue; } self->credits[ i ] = 0; } @@ -1069,7 +1070,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, } // base is under attack warning if DCC'd - if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && + if( targ->buildableTeam == TEAM_HUMANS && G_IsDCCBuilt( ) && level.time > level.humanBaseAttackTimer ) { level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; @@ -1085,7 +1086,8 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, // add to the attacker's hit counter if( attacker->client && targ != attacker && targ->health > 0 && targ->s.eType != ET_MISSILE - && targ->s.eType != ET_GENERAL ) + && targ->s.eType != ET_GENERAL + && mod != MOD_DECONSTRUCT ) { if( OnSameTeam( targ, attacker ) ) attacker->client->ps.persistant[ PERS_HITS ]--; diff --git a/src/game/g_local.h b/src/game/g_local.h index 001c7d9..944140f 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -198,16 +198,12 @@ struct gentity_s gentity_t *parentNode; // for creep and defence/spawn dependencies qboolean active; // for power repeater, but could be useful elsewhere qboolean locked; // used for turret tracking - qboolean powered; // for human buildables int builtBy; // clientNum of person that built this - int dcc; // number of controlling dccs qboolean spawned; // whether or not this buildable has finished spawning int shrunkTime; // time when a barricade shrunk or zero int buildTime; // when this buildable was built int animTime; // last animation change int time1000; // timer evaluated every second - qboolean deconstruct; // deconstruct if no BP left - int deconstructTime; // time at which structure marked int overmindAttackTimer; int overmindDyingTimer; int overmindSpawnsTimer; @@ -243,10 +239,24 @@ struct gentity_s qboolean pointAgainstWorld; // don't use the bbox for map collisions - int buildPointZone; // index for zone - int usesBuildPointZone; // does it use a zone? + vec3_t cuboidSize; - vec3_t cuboidSize; + //power grid + qboolean requiresPower; // false for telenodes, cuboids, etc. + qboolean isPowerSource; // true for all active elements (including the capbank) + + int powerNetwork; // which network is it in (0 is no network) + qboolean powered; // is this buildable powered? + qboolean surge; // true if the buildable requests high amount of power (resistance=surgeResistance) + // IMPORTANT NOTE: it should NOT depend on anything power-related to avoid + // unstable network states + qboolean surgePowered; // is surge powered? + float voltage; // voltage drop (load) or gain (source) + float current; // current flow + float resistance; // resistance (for sources it's their internal resistance) + + //mass + float storedBP; }; typedef enum @@ -485,16 +495,6 @@ void G_PrintSpawnQueue( spawnQueue_t *sq ); #define MAX_DAMAGE_REGION_TEXT 8192 #define MAX_DAMAGE_REGIONS 16 -// build point zone -typedef struct -{ - int active; - - int totalBuildPoints; - int queuedBuildPoints; - int nextQueueTime; -} buildPointZone_t; - // store locational damage regions typedef struct damageRegion_s { @@ -641,14 +641,6 @@ typedef struct int alienBuildPoints; int alienBuildPointQueue; int alienNextQueueTime; - int humanBuildPoints; - int humanBuildPointQueue; - int humanNextQueueTime; - - buildPointZone_t *buildPointZones; - - gentity_t *markedBuildables[ MAX_GENTITIES ]; - int numBuildablesForRemoval; int alienKills; int humanKills; @@ -801,8 +793,8 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, buildable_t G_IsPowered( vec3_t origin ); qboolean G_IsDCCBuilt( void ); -int G_FindDCC( gentity_t *self ); -gentity_t *G_Reactor( void ); +qboolean G_IsRTGBuilt( void ); +qboolean G_FindDCC( gentity_t *self ); gentity_t *G_Overmind( void ); qboolean G_FindCreep( gentity_t *self ); @@ -823,12 +815,7 @@ void G_BaseSelfDestruct( team_t team ); int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ); void G_QueueBuildPoints( gentity_t *self ); int G_GetBuildPoints( const vec3_t pos, team_t team ); -int G_GetMarkedBuildPoints( const vec3_t pos, team_t team ); qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned ); -gentity_t *G_PowerEntityForPoint( const vec3_t origin ); -gentity_t *G_PowerEntityForEntity( gentity_t *ent ); -gentity_t *G_RepeaterEntityForPoint( vec3_t origin ); -gentity_t *G_InPowerZone( gentity_t *self ); buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate ); void G_BuildLogSet( buildLog_t *log, gentity_t *ent ); void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ); @@ -836,6 +823,7 @@ void G_BuildLogRevert( int id ); const char *G_CuboidName(buildable_t buildable, const vec3_t cuboidSize, qboolean verbose); void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ); void G_RemoveUnbuiltBuildables( gentity_t *self ); +void G_UpdatePowerGrid( float dt ); // // g_utils.c @@ -1158,11 +1146,6 @@ extern vmCvar_t pmove_msec; extern vmCvar_t g_alienBuildPoints; extern vmCvar_t g_alienBuildQueueTime; -extern vmCvar_t g_humanBuildPoints; -extern vmCvar_t g_humanBuildQueueTime; -extern vmCvar_t g_humanRepeaterBuildPoints; -extern vmCvar_t g_humanRepeaterBuildQueueTime; -extern vmCvar_t g_humanRepeaterMaxZones; extern vmCvar_t g_humanStage; extern vmCvar_t g_humanCredits; extern vmCvar_t g_humanMaxStage; @@ -1182,8 +1165,6 @@ extern vmCvar_t g_disabledEquipment; extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; -extern vmCvar_t g_markDeconstruct; - extern vmCvar_t g_debugMapRotation; extern vmCvar_t g_currentMapRotation; extern vmCvar_t g_mapRotationNodes; @@ -1227,6 +1208,11 @@ extern vmCvar_t g_cuboidHealthLimit; extern vmCvar_t g_buildableDensityLimit; extern vmCvar_t g_buildableDensityLimitRange; +extern vmCvar_t g_buildableValueModifier; +extern vmCvar_t g_playerValueModifier; +extern vmCvar_t g_massYieldModifier; +extern vmCvar_t g_voltageModifier; + void trap_Print( const char *fmt ); void trap_Error( const char *fmt ); diff --git a/src/game/g_main.c b/src/game/g_main.c index 0f9c31c..1cbf897 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -86,11 +86,6 @@ vmCvar_t g_maxNameChanges; vmCvar_t g_alienBuildPoints; vmCvar_t g_alienBuildQueueTime; -vmCvar_t g_humanBuildPoints; -vmCvar_t g_humanBuildQueueTime; -vmCvar_t g_humanRepeaterBuildPoints; -vmCvar_t g_humanRepeaterBuildQueueTime; -vmCvar_t g_humanRepeaterMaxZones; vmCvar_t g_humanStage; vmCvar_t g_humanCredits; vmCvar_t g_humanMaxStage; @@ -110,8 +105,6 @@ vmCvar_t g_disabledEquipment; vmCvar_t g_disabledClasses; vmCvar_t g_disabledBuildables; -vmCvar_t g_markDeconstruct; - vmCvar_t g_debugMapRotation; vmCvar_t g_currentMapRotation; vmCvar_t g_mapRotationNodes; @@ -158,6 +151,11 @@ vmCvar_t g_cuboidMode; vmCvar_t g_buildableDensityLimit; vmCvar_t g_buildableDensityLimitRange; +vmCvar_t g_buildableValueModifier; +vmCvar_t g_playerValueModifier; +vmCvar_t g_massYieldModifier; +vmCvar_t g_voltageModifier; + // copy cvars that can be set in worldspawn so they can be restored later static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; @@ -232,11 +230,6 @@ static cvarTable_t gameCvarTable[ ] = { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse }, { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse }, - { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse }, { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse }, { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage }, @@ -261,8 +254,6 @@ static cvarTable_t gameCvarTable[ ] = { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, - { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse }, @@ -301,7 +292,12 @@ static cvarTable_t gameCvarTable[ ] = { &g_cuboidMode, "g_cuboidMode", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_buildableDensityLimit, "g_buildableDensityLimit", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, - { &g_buildableDensityLimitRange, "g_buildableDensityLimitRange", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse } + { &g_buildableDensityLimitRange, "g_buildableDensityLimitRange", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + + { &g_playerValueModifier, "g_playerValueModifier", "0.5", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_buildableValueModifier, "g_buildableValueModifier", "0.16", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_massYieldModifier, "g_massYieldModifier", "1", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, + { &g_voltageModifier, "g_voltageModifier", "1.0", CVAR_ARCHIVE | CVAR_SERVERINFO } }; static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); @@ -1197,7 +1193,6 @@ void G_CalculateBuildPoints( void ) { int i; buildable_t buildable; - buildPointZone_t *zone; // BP queue updates while( level.alienBuildPointQueue > 0 && @@ -1209,15 +1204,6 @@ void G_CalculateBuildPoints( void ) g_alienBuildQueueTime.integer ); } - while( level.humanBuildPointQueue > 0 && - level.humanNextQueueTime < level.time ) - { - level.humanBuildPointQueue--; - level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, - g_humanBuildPoints.integer, - g_humanBuildQueueTime.integer ); - } - // Sudden Death checks if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) { @@ -1226,7 +1212,6 @@ void G_CalculateBuildPoints( void ) trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); trap_SendServerCommand( -1, "announce suddendeath" ); level.suddenDeathWarning = TW_PASSED; - G_ClearDeconMarks( ); // Clear blueprints, or else structs that cost 0 BP can still be built after SD for( i = 0; i < level.maxclients; i++ ) @@ -1246,101 +1231,26 @@ void G_CalculateBuildPoints( void ) level.suddenDeathWarning = TW_IMMINENT; } - level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; - // Reset buildPointZones - for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) - { - buildPointZone_t *zone = &level.buildPointZones[ i ]; - - zone->active = qfalse; - zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; - } - // Iterate through entities for( i = MAX_CLIENTS; i < level.num_entities; i++ ) { gentity_t *ent = &g_entities[ i ]; - buildPointZone_t *zone; buildable_t buildable; int cost; if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) continue; - // mark a zone as active - if( ent->usesBuildPointZone ) - { - assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); - - zone = &level.buildPointZones[ ent->buildPointZone ]; - zone->active = qtrue; - } - // Subtract the BP from the appropriate pool buildable = ent->s.modelindex; cost = BG_Buildable( buildable, ent->cuboidSize )->buildPoints; if( ent->buildableTeam == TEAM_ALIENS ) level.alienBuildPoints -= cost; - if( buildable == BA_H_REPEATER ) - level.humanBuildPoints -= cost; - else if( buildable != BA_H_REACTOR ) - { - gentity_t *power = G_PowerEntityForEntity( ent ); - - if( power ) - { - if( power->s.modelindex == BA_H_REACTOR ) - level.humanBuildPoints -= cost; - else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) - level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; - } - } } - // Finally, update repeater zones and their queues - // note that this has to be done after the used BP is calculated - for( i = MAX_CLIENTS; i < level.num_entities; i++ ) - { - gentity_t *ent = &g_entities[ i ]; - - if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || - ent->buildableTeam != TEAM_HUMANS ) - continue; - - buildable = ent->s.modelindex; - - if( buildable != BA_H_REPEATER ) - continue; - - if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) - { - zone = &level.buildPointZones[ ent->buildPointZone ]; - - if( G_TimeTilSuddenDeath( ) > 0 ) - { - // BP queue updates - while( zone->queuedBuildPoints > 0 && - zone->nextQueueTime < level.time ) - { - zone->queuedBuildPoints--; - zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, - zone->totalBuildPoints, - g_humanRepeaterBuildQueueTime.integer ); - } - } - else - { - zone->totalBuildPoints = zone->queuedBuildPoints = 0; - } - } - } - - if( level.humanBuildPoints < 0 ) - level.humanBuildPoints = 0; - if( level.alienBuildPoints < 0 ) level.alienBuildPoints = 0; } @@ -2338,14 +2248,6 @@ void CheckCvars( void ) trap_Cvar_Set( "g_needpass", "0" ); } - // Unmark any structures for deconstruction when - // the server setting is changed - if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) - { - lastMarkDeconModCount = g_markDeconstruct.modificationCount; - G_ClearDeconMarks( ); - } - // If we change g_suddenDeathTime during a map, we need to update // when sd will begin if( g_suddenDeathTime.modificationCount != lastSDTimeModCount ) @@ -2354,24 +2256,6 @@ void CheckCvars( void ) level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; } - // If the number of zones changes, we need a new array - if( g_humanRepeaterMaxZones.integer != lastNumZones ) - { - buildPointZone_t *newZones; - size_t newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); - size_t oldsize = lastNumZones * sizeof( buildPointZone_t ); - - newZones = BG_Alloc( newsize ); - if( level.buildPointZones ) - { - Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); - BG_Free( level.buildPointZones ); - } - - level.buildPointZones = newZones; - lastNumZones = g_humanRepeaterMaxZones.integer; - } - level.frameMsec = trap_Milliseconds( ); } @@ -2586,6 +2470,9 @@ void G_RunFrame( int levelTime ) G_SpawnClients( TEAM_HUMANS ); G_CalculateAvgPlayers( ); G_UpdateZaps( msec ); + + // update the power grid + G_UpdatePowerGrid( 0.001f * msec ); // see if it is time to end the level CheckExitRules( ); diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index 4a28083..e4399a4 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -768,8 +768,8 @@ void CheckCkitRepair( gentity_t *ent ) if( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) { - if(BG_Buildable(traceEnt->s.modelindex,NULL)->cuboid) - if(!BG_CuboidAttributes(traceEnt->s.modelindex)->repairable) + if( BG_Buildable( traceEnt->s.modelindex, NULL )->cuboid ) + if( !BG_CuboidAttributes( traceEnt->s.modelindex )->repairable ) return; bHealth = BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->health; diff --git a/src/game/tremulous.h b/src/game/tremulous.h index bf60cd0..71d848e 100644 --- a/src/game/tremulous.h +++ b/src/game/tremulous.h @@ -600,10 +600,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define DC_SPLASHDAMAGE 50 #define DC_SPLASHRADIUS 100 #define DC_ATTACK_PERIOD 10000 // how often to spam "under attack" -#define DC_HEALRATE 4 +#define DC_HEALRATE 250 // +1 HP every this amount of time #define DC_RANGE 1000 #define DC_VALUE HBVM(DC_BP) -#define MAX_DCS_PER_BUILDABLE 2 #define ARMOURY_BP 10 #define ARMOURY_BT 15000 @@ -612,7 +611,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ARMOURY_SPLASHRADIUS 100 #define ARMOURY_VALUE HBVM(ARMOURY_BP) -#define REACTOR_BP 0 +#define REACTOR_BP 36 #define REACTOR_BT 30000 #define REACTOR_HEALTH HBHM(930) #define REACTOR_SPLASHDAMAGE 200 @@ -623,7 +622,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define REACTOR_ATTACK_DCC_REPEAT 1000 #define REACTOR_ATTACK_DCC_RANGE 150.0f #define REACTOR_ATTACK_DCC_DAMAGE 40 -#define REACTOR_VALUE HBVM(30) +#define REACTOR_VALUE HBVM(REACTOR_BP) #define REPEATER_BP 4 #define REPEATER_BT 15000 @@ -632,6 +631,86 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define REPEATER_SPLASHRADIUS 100 #define REPEATER_VALUE HBVM(REPEATER_BP) +#define CAPBANK_BP 10 +#define CAPBANK_BT 20000 +#define CAPBANK_HEALTH HBHM(310) +#define CAPBANK_SPLASHDAMAGE 70 +#define CAPBANK_SPLASHRADIUS 140 +#define CAPBANK_VALUE HBVM(CAPBANK_BP) + +#define RTG_BP 14 +#define RTG_BT 20000 +#define RTG_HEALTH HBHM(460) +#define RTG_SPLASHDAMAGE 120 +#define RTG_SPLASHRADIUS 150 +#define RTG_VALUE HBVM(15) +#define RTG_YIELD 0.15 +#define RTG_STORAGE 15 + +#define REFINERY_BP 16 +#define REFINERY_BT 20000 +#define REFINERY_HEALTH HBHM(310) +#define REFINERY_SPLASHDAMAGE 100 +#define REFINERY_SPLASHRADIUS 150 +#define REFINERY_VALUE HBVM(REFINERY_BP) +#define REFINERY_YIELD 0.35 // at 2Hz +#define REFINERY_STORAGE 30 + +/* + * POWER GRID settings + * + * All units are SI: + * resistance (R) - ohms + * voltage (V) - volts + * current (I) - amperes + * capacity (C) - farads + */ + +//settings for buildables that are not a part of the power grid +#define DEFAULT_POWER_SETTINGS qfalse, qfalse, 0.0f, 0.0f, qfalse + +#define RESISTANCE(i,pc) (POWER_VOLTAGE/(i)*(pc)) + +#define POWER_VOLTAGE 100.0f + +#define RTG_RESISTANCE 5.0f + +#define REACTOR_RESISTANCE 1.0f + +#define CAPBANK_RESISTANCE 0.05f +#define CAPBANK_CAPACITY 2.0f + +#define PREBUILD_CURRENT 7.0f +#define PREBUILD_RESISTANCE RESISTANCE(PREBUILD_CURRENT,0.8f) + +#define MEDISTAT_I_IDLE 0.25f +#define MEDISTAT_R_IDLE RESISTANCE(MEDISTAT_I_IDLE,0.35f) +#define MEDISTAT_I_ACTIVE 1.0f +#define MEDISTAT_R_ACTIVE RESISTANCE(MEDISTAT_I_ACTIVE,0.75f) + +#define ARMOURY_CURRENT 0.1f +#define ARMOURY_RESISTANCE RESISTANCE(ARMOURY_CURRENT,0.25f) + +#define TESLAGEN_I_IDLE 0.6f +#define TESLAGEN_R_IDLE RESISTANCE(TESLAGEN_I_IDLE,0.52f) +#define TESLAGEN_I_ACTIVE 5.0f +#define TESLAGEN_R_ACTIVE RESISTANCE(TESLAGEN_I_ACTIVE,0.75f) + +#define MGTURRET_I_IDLE 0.3f +#define MGTURRET_R_IDLE RESISTANCE(MGTURRET_I_IDLE,0.6f) +#define MGTURRET_I_ACTIVE 2.0f +#define MGTURRET_R_ACTIVE RESISTANCE(MGTURRET_I_ACTIVE,0.75f) + +#define DC_I_IDLE 0.4f +#define DC_R_IDLE RESISTANCE(DC_I_IDLE,0.5f) +#define DC_I_ACTIVE 5.0f +#define DC_R_ACTIVE RESISTANCE(DC_I_ACTIVE,0.75f) + +#define REFINERY_I_IDLE 0.25f +#define REFINERY_R_IDLE RESISTANCE(REFINERY_I_IDLE,0.55f) +#define REFINERY_I_ACTIVE 4.0f +#define REFINERY_R_ACTIVE RESISTANCE(REFINERY_I_ACTIVE,0.75f) + /* * HUMAN misc */ @@ -664,6 +743,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HUMAN_BUILDABLE_INACTIVE_TIME 90000 +#define CKIT_STORAGE 36 + /* * Misc */ -- cgit