diff --git a/data/base/audio/audio.json b/data/base/audio/audio.json index 43874ec5561..56365d75391 100644 --- a/data/base/audio/audio.json +++ b/data/base/audio/audio.json @@ -450,7 +450,8 @@ { "fileName": "scream.ogg", "loop": false, "range": 1800, "volume": 100 }, { "fileName": "scream2.ogg", "loop": false, "range": 1800, "volume": 100 }, { "fileName": "scream3.ogg", "loop": false, "range": 1800, "volume": 100 }, - { "fileName": "silence.ogg", "loop": false, "range": 1800, "volume": 100 } + { "fileName": "silence.ogg", "loop": false, "range": 1800, "volume": 100 }, + { "fileName": "shield-hit.ogg", "loop": false, "range": 1800, "volume": 100 } ] }, "Extra": { diff --git a/data/base/audio/sfx/misc/shield-hit.ogg b/data/base/audio/sfx/misc/shield-hit.ogg new file mode 100644 index 00000000000..d4d12671b48 Binary files /dev/null and b/data/base/audio/sfx/misc/shield-hit.ogg differ diff --git a/data/base/wrf/audio.wrf b/data/base/wrf/audio.wrf index 8c7f88b35c9..c857b834782 100644 --- a/data/base/wrf/audio.wrf +++ b/data/base/wrf/audio.wrf @@ -338,6 +338,7 @@ file WAV "scream.ogg" file WAV "scream2.ogg" file WAV "scream3.ogg" file WAV "silence.ogg" +file WAV "shield-hit.ogg" directory "audio/extra" file WAV "lndgzne.ogg" file WAV "nmedeted.ogg" diff --git a/lib/sound/audio_id.cpp b/lib/sound/audio_id.cpp index 7ab707b08fe..4d90178fb45 100644 --- a/lib/sound/audio_id.cpp +++ b/lib/sound/audio_id.cpp @@ -439,6 +439,7 @@ static AUDIO_ID_MAP asAudioID[] = {ID_SOUND_BARB_SCREAM2, "scream2.ogg"}, {ID_SOUND_BARB_SCREAM3, "scream3.ogg"}, {ID_SOUND_OF_SILENCE, "silence.ogg"}, + {ID_SOUND_SHIELD_HIT, "shield-hit.ogg"}, /* Extra */ {ID_SOUND_LANDING_ZONE, "lndgzne.ogg"}, diff --git a/lib/sound/audio_id.h b/lib/sound/audio_id.h index e33f820144a..7d88f8a5b26 100644 --- a/lib/sound/audio_id.h +++ b/lib/sound/audio_id.h @@ -428,6 +428,7 @@ enum INGAME_AUDIO ID_SOUND_BARB_SCREAM2, ID_SOUND_BARB_SCREAM3, ID_SOUND_OF_SILENCE, + ID_SOUND_SHIELD_HIT, /* Extra */ diff --git a/src/combat.cpp b/src/combat.cpp index a7da53cf35b..8b905373c81 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -28,6 +28,11 @@ #include "lib/framework/fixedpoint.h" #include "lib/netplay/sync_debug.h" +#include "lib/ivis_opengl/ivisdef.h" + +#include "lib/sound/audio.h" +#include "lib/sound/audio_id.h" + #include "action.h" #include "combat.h" #include "difficulty.h" @@ -38,6 +43,12 @@ #include "qtscript.h" #include "order.h" #include "objmem.h" +#include "effects.h" + + + +#define DROID_SHIELD_DAMAGE_SPREAD (16 - rand()%32) +#define DROID_SHIELD_PARTICLES (6 + rand()%8) /* Fire a weapon at something */ bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot) @@ -485,6 +496,36 @@ int32_t objDamage(BASE_OBJECT *psObj, unsigned damage, unsigned originalhp, WEAP return -(int64_t)65536 * psObj->body / originalhp; } + // Drain shields first + if (psObj->type == OBJ_DROID) { + DROID *psDroid = castDroid(psObj); + + if (psDroid->shieldPoints > 0) { + if (psDroid->shieldPoints > actualDamage) { + psDroid->shieldPoints -= actualDamage; + actualDamage = 0; + } else { + actualDamage -= psDroid->shieldPoints; + psDroid->shieldPoints = 0; + // shields are interrupted, wait for a while until regeneration starts again + psDroid->shieldInterruptRegenTime = psDroid->time; + psDroid->shieldRegenTime = psDroid->time; + } + + Vector3i dv; + dv.y = psDroid->pos.z; + dv.y += (psDroid->sDisplay.imd->max.y * 2); + + for (uint32_t i = 0; i < DROID_SHIELD_PARTICLES; i++) { + dv.x = psDroid->pos.x + DROID_SHIELD_DAMAGE_SPREAD; + dv.z = psDroid->pos.y + DROID_SHIELD_DAMAGE_SPREAD; + addEffect(&dv, EFFECT_FIREWORK, FIREWORK_TYPE_STARBURST, false, nullptr, 0, gameTime - deltaGameTime + 1); + } + + audio_PlayStaticTrack(psDroid->pos.x, psDroid->pos.y, ID_SOUND_SHIELD_HIT); + } + } + // Subtract the dealt damage from the droid's remaining body points psObj->body -= actualDamage; diff --git a/src/display3d.cpp b/src/display3d.cpp index 6ece53e1847..856cf43e1ef 100644 --- a/src/display3d.cpp +++ b/src/display3d.cpp @@ -3424,6 +3424,14 @@ bool eitherSelected(DROID *psDroid) static void queueDroidPowerBarsRects(DROID *psDroid, bool drawBox, BatchedMultiRectRenderer& batchedMultiRectRenderer, size_t rectGroup) { UDWORD damage = PERCENT(psDroid->body, psDroid->originalBody); + UDWORD shields = 0; + + if (psDroid->shieldPoints >= 0) { + int maxShieldPoints = droidGetMaxShieldPoints(psDroid); + shields = PERCENT(psDroid->shieldPoints, maxShieldPoints); + shields = static_cast((float)psDroid->shieldPoints / (float)maxShieldPoints * (float)psDroid->sDisplay.screenR); + shields *= 2; + } PIELIGHT powerCol = WZCOL_BLACK; PIELIGHT powerColShadow = WZCOL_BLACK; @@ -3464,6 +3472,7 @@ static void queueDroidPowerBarsRects(DROID *psDroid, bool drawBox, BatchedMultiR batchedMultiRectRenderer.addRect(PIERECT_DrawRequest(psDroid->sDisplay.screenX - psDroid->sDisplay.screenR - 1, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 2, psDroid->sDisplay.screenX + psDroid->sDisplay.screenR + 1, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 6, WZCOL_RELOAD_BACKGROUND), rectGroup); batchedMultiRectRenderer.addRect(PIERECT_DrawRequest(psDroid->sDisplay.screenX - psDroid->sDisplay.screenR, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 3, psDroid->sDisplay.screenX - psDroid->sDisplay.screenR + damage, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 4, powerCol), rectGroup); batchedMultiRectRenderer.addRect(PIERECT_DrawRequest(psDroid->sDisplay.screenX - psDroid->sDisplay.screenR, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 4, psDroid->sDisplay.screenX - psDroid->sDisplay.screenR + damage, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 5, powerColShadow), rectGroup); + batchedMultiRectRenderer.addRect(PIERECT_DrawRequest(psDroid->sDisplay.screenX - psDroid->sDisplay.screenR, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 8, psDroid->sDisplay.screenX - psDroid->sDisplay.screenR + shields, psDroid->sDisplay.screenY + psDroid->sDisplay.screenR + 6, WZCOL_LBLUE), rectGroup); } static void queueDroidEnemyHealthBarsRects(DROID *psDroid, BatchedMultiRectRenderer& batchedMultiRectRenderer, size_t rectGroup) diff --git a/src/droid.cpp b/src/droid.cpp index e3e0ed75179..e17c439af01 100644 --- a/src/droid.cpp +++ b/src/droid.cpp @@ -911,6 +911,9 @@ void droidUpdate(DROID *psDroid) droidUpdateDroidSelfRepair(psDroid); } + if (bMultiPlayer) { + droidUpdateShields(psDroid); + } /* Update the fire damage data */ if (psDroid->periodicalDamageStart != 0 && psDroid->periodicalDamageStart != gameTime - deltaGameTime) // -deltaGameTime, since projectiles are updated after droids. @@ -953,6 +956,44 @@ void droidUpdate(DROID *psDroid) CHECK_DROID(psDroid); } +void droidUpdateShields(DROID *psDroid) { + if (hasCommander(psDroid) || psDroid->droidType == DROID_COMMAND) { + if (psDroid->shieldPoints < 0) { + psDroid->shieldPoints = 0; + psDroid->shieldRegenTime = gameTime; + psDroid->shieldInterruptRegenTime = gameTime; + + } else { + if (gameTime - psDroid->shieldInterruptRegenTime >= droidCalculateShieldInterruptRegenTime(psDroid)) { + if (gameTime - psDroid->shieldRegenTime >= droidCalculateShieldRegenTime(psDroid)) { + for (uint32_t i = 0; i < 4; i++) { + if (psDroid->shieldPoints < droidGetMaxShieldPoints(psDroid)) { + psDroid->shieldPoints += 1; + } + } + psDroid->shieldRegenTime = gameTime; + } + } + } + } else { + // unit has lost commander, shields are down! + psDroid->shieldPoints = -1; + } +} + +uint32_t droidCalculateShieldRegenTime(DROID *psDroid) { + return DROID_INITIAL_SHIELD_REGEN_TIME - (DROID_SHIELD_REGEN_TIME_DEC * getDroidLevel(psDroid)); +} + +uint32_t droidCalculateShieldInterruptRegenTime(DROID *psDroid) { + return DROID_INITIAL_SHIELD_INTERRUPT_REGEN_TIME - (DROID_SHIELD_INTERRUPT_REGEN_TIME_DEC * getDroidLevel(psDroid)); +} + +int32_t droidGetMaxShieldPoints(DROID *psDroid) { + double percent = static_cast(psDroid->originalBody) / 100.0f; + return std::round(percent * (DROID_INITIAL_SHILED_POINTS_PERCENT + DROID_ADDITVE_SHILED_POINTS_PERCENT * getDroidLevel(psDroid))); +} + /* See if a droid is next to a structure */ static bool droidNextToStruct(DROID *psDroid, STRUCTURE *psStruct) { @@ -1723,6 +1764,7 @@ DROID *reallyBuildDroid(const DROID_TEMPLATE *pTemplate, Position pos, UDWORD pl droid.experience = 0; } droid.kills = 0; + droid.shieldPoints = -1; droidSetBits(pTemplate, &droid); diff --git a/src/droid.h b/src/droid.h index 816b9a3b6b9..ced8cba3806 100644 --- a/src/droid.h +++ b/src/droid.h @@ -118,6 +118,18 @@ int32_t droidDamage(DROID *psDroid, unsigned damage, WEAPON_CLASS weaponClass, W /* The main update routine for all droids */ void droidUpdate(DROID *psDroid); +/* Update droid shields. */ +void droidUpdateShields(DROID *psDroid); + +/* Calculate the droid's shield regeneration step time */ +uint32_t droidCalculateShieldRegenTime(DROID *psDroid); + +/* Calculate the droid's shield interruption time */ +uint32_t droidCalculateShieldInterruptRegenTime(DROID *psDroid); + +/* Get droid maximum shield points */ +int32_t droidGetMaxShieldPoints(DROID *psDroid); + /* Set up a droid to build a structure - returns true if successful */ enum DroidStartBuild {DroidStartBuildFailed, DroidStartBuildSuccess, DroidStartBuildPending}; DroidStartBuild droidStartBuild(DROID *psDroid); diff --git a/src/droiddef.h b/src/droiddef.h index d84255fd1fe..9050f84c020 100644 --- a/src/droiddef.h +++ b/src/droiddef.h @@ -51,6 +51,13 @@ //defines how many times to perform the iteration on looking for a blank location #define LOOK_FOR_EMPTY_TILE 20 +#define DROID_INITIAL_SHILED_POINTS_PERCENT 5.0f +#define DROID_ADDITVE_SHILED_POINTS_PERCENT 2.5f +#define DROID_INITIAL_SHIELD_REGEN_TIME 32 +#define DROID_SHIELD_REGEN_TIME_DEC 2 +#define DROID_INITIAL_SHIELD_INTERRUPT_REGEN_TIME 2000 +#define DROID_SHIELD_INTERRUPT_REGEN_TIME_DEC 100 + typedef std::vector OrderList; struct DROID_TEMPLATE : public BASE_STATS @@ -150,6 +157,9 @@ struct DROID : public BASE_OBJECT UDWORD baseSpeed; ///< the base speed dependent on propulsion type UDWORD originalBody; ///< the original body points uint32_t experience; + int32_t shieldPoints; + UDWORD shieldRegenTime; + UDWORD shieldInterruptRegenTime; uint32_t kills; UDWORD lastFrustratedTime; ///< Set when eg being stuck; used for eg firing indiscriminately at map features to clear the way SWORD resistance; ///< used in Electronic Warfare