mirror of
https://github.com/ssstolk/rts4ds.git
synced 2025-06-19 01:15:32 -04:00
746 lines
38 KiB
C
746 lines
38 KiB
C
// SPDX-License-Identifier: MIT
|
|
// Copyright © 2007-2025 Sander Stolk
|
|
|
|
#include "projectiles.h"
|
|
|
|
#include "fileio.h"
|
|
#include "view.h"
|
|
#include "sprites.h"
|
|
#include "environment.h"
|
|
#include "overlay.h"
|
|
#include "structures.h"
|
|
#include "units.h"
|
|
#include "explosions.h"
|
|
#include "radar.h"
|
|
#include "factions.h"
|
|
#include "soundeffects.h"
|
|
#include "rumble.h"
|
|
|
|
struct ProjectileInfo projectileInfo[MAX_DIFFERENT_PROJECTILES];
|
|
struct Projectile projectile[MAX_PROJECTILES_ON_MAP];
|
|
|
|
unsigned int projectileImpassableOverEnvironment[(NUMBER_OF_ENVIRONMENT_TILE_GRAPHICS + 31) / 32];
|
|
|
|
#define MAX_RADIUS_PROJECTILE_FOR_SINGLE_STRUCTURE_HIT 10
|
|
|
|
static int projectileStructureHit[MAX_RADIUS_PROJECTILE_FOR_SINGLE_STRUCTURE_HIT * MAX_RADIUS_PROJECTILE_FOR_SINGLE_STRUCTURE_HIT];
|
|
static int projectileStructureHitAmount;
|
|
|
|
|
|
|
|
void initProjectiles() {
|
|
char oneline[256];
|
|
int amountOfProjectiles = 0;
|
|
int i, j;
|
|
FILE *fp;
|
|
|
|
// get the names of all available projectiles
|
|
fp = openFile("projectiles.ini", FS_PROJECT_FILE);
|
|
readstr(fp, oneline);
|
|
sscanf(oneline, "AMOUNT-OF-PROJECTILES %i", &amountOfProjectiles);
|
|
if (amountOfProjectiles > MAX_DIFFERENT_PROJECTILES)
|
|
errorSI("Too many types of projectiles exist. Limit is:", MAX_DIFFERENT_PROJECTILES);
|
|
for (i=0; i<amountOfProjectiles; i++) {
|
|
readstr(fp, oneline);
|
|
replaceEOLwithEOF(oneline, 255);
|
|
strcpy(projectileInfo[i].name, oneline);
|
|
projectileInfo[i].enabled = 1;
|
|
}
|
|
closeFile(fp);
|
|
|
|
// get information on each and every available projectile
|
|
for (i=0; i<amountOfProjectiles; i++) {
|
|
fp = openFile(projectileInfo[i].name, FS_PROJECTILES_INFO);
|
|
|
|
// GENERAL section
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[GENERAL]", strlen("[GENERAL]")));
|
|
readstr(fp, oneline);
|
|
readstr(fp, oneline);
|
|
if (!strncmp(oneline + strlen("Type="), "Shot", strlen("Shot")))
|
|
projectileInfo[i].type = PT_SHOT;
|
|
else if (!strncmp(oneline + strlen("Type="), "Aerial Shot", strlen("Aerial Shot")))
|
|
projectileInfo[i].type = PT_AERIAL_SHOT;
|
|
else if (!strncmp(oneline + strlen("Type="), "Bullet", strlen("Bullet")))
|
|
projectileInfo[i].type = PT_BULLET;
|
|
else if (!strncmp(oneline + strlen("Type="), "Aerial Bullet", strlen("Aerial Bullet")))
|
|
projectileInfo[i].type = PT_AERIAL_BULLET;
|
|
else if (!strncmp(oneline + strlen("Type="), "Rocket", strlen("Rocket")))
|
|
projectileInfo[i].type = PT_ROCKET;
|
|
else if (!strncmp(oneline + strlen("Type="), "Aerial Rocket", strlen("Aerial Rocket")))
|
|
projectileInfo[i].type = PT_AERIAL_ROCKET;
|
|
else if (!strncmp(oneline + strlen("Type="), "Shell", strlen("Shell")))
|
|
projectileInfo[i].type = PT_SHELL;
|
|
else if (!strncmp(oneline + strlen("Type="), "Aerial Shell", strlen("Aerial Shell")))
|
|
projectileInfo[i].type = PT_AERIAL_SHELL;
|
|
else {
|
|
replaceEOLwithEOF(oneline, 255);
|
|
error("Projectile had unrecognised type called:", oneline + strlen("Type="));
|
|
}
|
|
|
|
// MEASUREMENTS section
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[MEASUREMENTS]", strlen("[MEASUREMENTS]")));
|
|
readstr(fp, oneline);
|
|
sscanf(oneline, "Size=%i", &j);
|
|
projectileInfo[i].graphics_size = (unsigned int) j;
|
|
|
|
// EXPLOSION section
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[EXPLOSION]", strlen("[EXPLOSION]")));
|
|
readstr(fp, oneline);
|
|
projectileInfo[i].explosion_info = -1;
|
|
for (j=0; j<MAX_DIFFERENT_EXPLOSIONS && explosionInfo[j].enabled; j++) {
|
|
if (!strncmp(oneline + strlen("Explosion="), explosionInfo[j].name, strlen(explosionInfo[j].name))) {
|
|
projectileInfo[i].explosion_info = j;
|
|
break;
|
|
}
|
|
}
|
|
if (projectileInfo[i].type == PT_SHELL || projectileInfo[i].type == PT_AERIAL_SHELL) {
|
|
readstr(fp, oneline);
|
|
sscanf(oneline, "Radius=%i", &projectileInfo[i].explosion_radius);
|
|
}
|
|
|
|
// FORCE section
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[FORCE]", strlen("[FORCE]")));
|
|
readstr(fp, oneline);
|
|
sscanf(oneline, "Power=%i", &projectileInfo[i].power);
|
|
|
|
// MOVEMENT section
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[MOVEMENT]", strlen("[MOVEMENT]")));
|
|
readstr(fp, oneline);
|
|
sscanf(oneline, "Speed=%i", &projectileInfo[i].original_speed);
|
|
if (projectileInfo[i].original_speed > 60)
|
|
error("Found a projectile speed > 60", "");
|
|
projectileInfo[i].original_speed = (projectileInfo[i].original_speed * SCREEN_REFRESH_RATE) / FPS;
|
|
|
|
// EFFECT sections
|
|
for (j=0; j<EME_COUNT; j++)
|
|
projectileInfo[i].effectModifier[j] = EMT_NORMAL;
|
|
|
|
do {
|
|
readstr(fp, oneline);
|
|
} while (strncmp(oneline, "[EFFECT", strlen("[EFFECT")));
|
|
for (j=EMT_NIL; j<=EMT_INSTANT; j++) {
|
|
readstr(fp, oneline);
|
|
while (oneline[0] != '[') {
|
|
// "Friendly", "Enemy", "Structures", "Units, Foot", "Units, Wheeled", "Units, Tracked"
|
|
if (oneline[0] == 'F')
|
|
projectileInfo[i].effectModifier[EME_FRIENDLY] = j;
|
|
else if (oneline[0] == 'E')
|
|
projectileInfo[i].effectModifier[EME_ENEMY] = j;
|
|
else if (oneline[0] == 'S')
|
|
projectileInfo[i].effectModifier[EME_STRUCTURES] = j;
|
|
else if (oneline[7] == 'F')
|
|
projectileInfo[i].effectModifier[EME_UNIT_TYPES + UT_FOOT] = j;
|
|
else if (oneline[7] == 'W')
|
|
projectileInfo[i].effectModifier[EME_UNIT_TYPES + UT_WHEELED] = j;
|
|
else if (oneline[7] == 'T')
|
|
projectileInfo[i].effectModifier[EME_UNIT_TYPES + UT_TRACKED] = j;
|
|
else if (oneline[7] == 'A')
|
|
projectileInfo[i].effectModifier[EME_UNIT_TYPES + UT_AERIAL] = j;
|
|
else
|
|
error("A flawed effect-entry found:", oneline);
|
|
readstr(fp, oneline);
|
|
}
|
|
}
|
|
|
|
closeFile(fp);
|
|
}
|
|
for (i=amountOfProjectiles; i<MAX_DIFFERENT_PROJECTILES; i++) // setting all unused ones to disabled
|
|
projectileInfo[i].enabled = 0;
|
|
}
|
|
|
|
void initProjectilesSpeed() {
|
|
int i;
|
|
int gameSpeed = getGameSpeed();
|
|
|
|
for (i=0; i<MAX_DIFFERENT_PROJECTILES && projectileInfo[i].enabled; i++)
|
|
projectileInfo[i].speed = (projectileInfo[i].original_speed * gameSpeed) / 2;
|
|
}
|
|
|
|
void initProjectilesWithScenario() {
|
|
int i;
|
|
struct Projectile *curProjectile = projectile;
|
|
|
|
for (i=0; i<MAX_PROJECTILES_ON_MAP; i++, curProjectile++)
|
|
curProjectile->enabled = 0;
|
|
|
|
initProjectilesSpeed();
|
|
}
|
|
|
|
|
|
// has some tan(X) * 1000 values hardcoded.
|
|
enum ProjectilePositioned positionProjectileToFace(int curX, int curY, int tgtX, int tgtY) {
|
|
int diffX = tgtX - curX;
|
|
int diffY = tgtY - curY;
|
|
|
|
if (diffX == 0) {
|
|
if (diffY >= 0)
|
|
return PP_DOWN;
|
|
return PP_UP;
|
|
}
|
|
|
|
int asc1000 = (abs(diffY) * 1000) / abs(diffX);
|
|
int section = 0;
|
|
if (asc1000 > 199) { // section = 0;
|
|
if (asc1000 <= 668) section = 1;
|
|
else if (asc1000 <= 1497) section = 2;
|
|
else if (asc1000 <= 5027) section = 3;
|
|
else section = 4;
|
|
}
|
|
|
|
if (diffY < 0) // upper half
|
|
return (diffX < 0) ?
|
|
((PP_LEFT + section) % (PP_LEFT_UP_UP + 1)) : // upper-left part
|
|
(PP_RIGHT - section); // upper-right part
|
|
// lower half
|
|
return (diffX < 0) ?
|
|
(PP_LEFT - section) : // lower-left part
|
|
(PP_RIGHT + section); // lower-right part
|
|
}
|
|
|
|
int addProjectile(enum Side side, int curX, int curY, int tgtX, int tgtY, int info, int shot) {
|
|
int i;
|
|
int absDX, absDY;
|
|
struct Projectile *curProjectile = projectile;
|
|
|
|
if (info < 0) // a unit or structure which has no weapon tried to create a new projectile
|
|
return -1;
|
|
|
|
for (i=0; i<MAX_PROJECTILES_ON_MAP; i++, curProjectile++) {
|
|
if (!curProjectile->enabled) {
|
|
curProjectile->enabled = 1;
|
|
curProjectile->info = info;
|
|
curProjectile->side = side;
|
|
if (projectileInfo[info].type >= PT_ROCKET)
|
|
curProjectile->positioned = positionProjectileToFace(curX, curY, tgtX, tgtY);
|
|
curProjectile->src_x = curX * 16 + 8;
|
|
curProjectile->src_y = curY * 16 + 8;
|
|
curProjectile->tgt_x = tgtX * 16 + 8;
|
|
curProjectile->tgt_y = tgtY * 16 + 8;
|
|
curProjectile->x = curProjectile->src_x;
|
|
curProjectile->y = curProjectile->src_y;
|
|
absDX = abs(tgtX - curX) * 16;
|
|
absDY = abs(tgtY - curY) * 16;
|
|
curProjectile->time_required = square_root(absDX*absDX + absDY*absDY) / projectileInfo[info].speed;
|
|
curProjectile->timer = 0;
|
|
playSoundeffectControlled(SE_PROJECTILE + 2*info + shot, volumePercentageOfLocation(curX, curY, DISTANCE_TO_HALVE_SE_PROJECTILE), soundeffectPanningOfLocation(curX, curY));
|
|
|
|
if (curX >= getViewCurrentX() && curX < getViewCurrentX() + HORIZONTAL_WIDTH &&
|
|
curY >= getViewCurrentY() && curY < getViewCurrentY() + HORIZONTAL_HEIGHT)
|
|
addRumble(5, 1);
|
|
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
void drawProjectiles() {
|
|
int i;
|
|
int x, y, graphics;
|
|
int hflip, vflip;
|
|
int lowX, highX, lowY, highY;
|
|
struct Projectile *curProjectile = projectile;
|
|
struct ProjectileInfo *curProjectileInfo;
|
|
|
|
lowX = getViewCurrentX() * 16;
|
|
highX = (getViewCurrentX() + HORIZONTAL_WIDTH) * 16;
|
|
lowY = getViewCurrentY() * 16;
|
|
highY = (getViewCurrentY() + HORIZONTAL_HEIGHT) * 16;
|
|
|
|
for (i=0; i<MAX_PROJECTILES_ON_MAP; i++, curProjectile++) {
|
|
if (curProjectile->enabled && projectileInfo[curProjectile->info].graphics_size > 0 &&
|
|
curProjectile->x >= lowX && curProjectile->x < highX &&
|
|
curProjectile->y >= lowY && curProjectile->y < highY) {
|
|
// bullet needs to be displayed
|
|
curProjectileInfo = &projectileInfo[curProjectile->info];
|
|
graphics = curProjectileInfo->graphics_offset;
|
|
hflip = 0;
|
|
vflip = 0;
|
|
x = (curProjectile->x - lowX) - (8 << curProjectileInfo->graphics_size) / 2;
|
|
y = ((curProjectile->y - lowY) - (8 << curProjectileInfo->graphics_size) / 2) + ACTION_BAR_SIZE * 16;
|
|
if (curProjectileInfo->type >= PT_ROCKET) {
|
|
// different directions: up, right-up-up, right-up, right-right-up, right
|
|
if (curProjectile->positioned <= PP_RIGHT)
|
|
graphics += math_power(4, (int) curProjectileInfo->graphics_size - 1) * ((int) curProjectile->positioned);
|
|
else if (curProjectile->positioned <= PP_DOWN) {
|
|
vflip = 1;
|
|
graphics += math_power(4, (int) curProjectileInfo->graphics_size - 1) * (PP_DOWN - ((int) curProjectile->positioned));
|
|
} else if (curProjectile->positioned <= PP_LEFT) {
|
|
vflip = 1;
|
|
hflip = 1;
|
|
graphics += math_power(4, (int) curProjectileInfo->graphics_size - 1) * (((int) curProjectile->positioned) - PP_DOWN);
|
|
} else {
|
|
hflip = 1;
|
|
graphics += math_power(4, (int) curProjectileInfo->graphics_size - 1) * ((PP_LEFT_UP_UP + 1) - ((int) curProjectile->positioned));
|
|
}
|
|
}
|
|
setSpritePlayScreen(y, ATTR0_SQUARE,
|
|
x, curProjectileInfo->graphics_size, hflip&1, vflip&1,
|
|
2, PS_SPRITES_PAL_PROJECTILES, graphics);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void doProjectileImpact(struct Projectile *curProjectile, struct ProjectileInfo *curProjectileInfo, struct Structure *structure, struct StructureInfo *structureInfo, struct Unit *unit, struct UnitInfo *unitInfo) {
|
|
struct EnvironmentLayout *environmentLayout;
|
|
enum EffectModifierType effectModifier;
|
|
int structureId, unitId;
|
|
int damage;
|
|
int j;
|
|
int tile = TILE_FROM_XY(curProjectile->x / 16, curProjectile->y / 16);
|
|
|
|
environmentLayout = &environment.layout[tile];
|
|
unitId = environmentLayout->contains_unit;
|
|
if (unitId >= 0) { // hit unit
|
|
if (unit[unitId].armour > 0 && unit[unitId].armour != INFINITE_ARMOUR) {
|
|
damage = curProjectileInfo->power;
|
|
effectModifier = curProjectileInfo->effectModifier[!unit[unitId].side != !curProjectile->side];
|
|
if (effectModifier == EMT_NORMAL)
|
|
effectModifier = curProjectileInfo->effectModifier[EME_UNIT_TYPES + unitInfo[unit[unitId].info].type];
|
|
switch (effectModifier) {
|
|
case EMT_NORMAL:
|
|
break;
|
|
case EMT_NIL:
|
|
damage = 0;
|
|
break;
|
|
case EMT_DIMINISHED:
|
|
damage = (damage * 70) / 100;
|
|
break;
|
|
case EMT_INCREASED:
|
|
damage = (damage * 130) / 100;
|
|
break;
|
|
case EMT_INSTANT:
|
|
damage *= unitInfo[unit[unitId].info].max_armour;
|
|
}
|
|
unit[unitId].armour -= damage;
|
|
if (curProjectileInfo->power < 0) { // healing effect
|
|
if (unit[unitId].armour > unitInfo[unit[unitId].info].max_armour)
|
|
unit[unitId].armour = unitInfo[unit[unitId].info].max_armour;
|
|
if (unit[unitId].armour >= unitInfo[unit[unitId].info].max_armour / 4)
|
|
unit[unitId].smoke_time = 0;
|
|
doUnitLogicHealing(unitId, TILE_FROM_XY(curProjectile->src_x / 16, curProjectile->src_y / 16));
|
|
} else { // destroying effect
|
|
|
|
displayHitRadar(curProjectile->x / 16, curProjectile->y / 16);
|
|
|
|
if (unit[unitId].armour <= 0 && unitInfo[unit[unitId].info].type == UT_FOOT)
|
|
addOverlay(curProjectile->x / 16, curProjectile->y / 16, OT_DEAD_FOOT, 0);
|
|
else {
|
|
if (unit[unitId].armour < unitInfo[unit[unitId].info].max_armour / 4)
|
|
unit[unitId].smoke_time = UNIT_SMOKE_DURATION + (unit[unitId].smoke_time % (3 * UNIT_SINGLE_SMOKE_DURATION));
|
|
doUnitLogicHit(unitId, TILE_FROM_XY(curProjectile->src_x / 16, curProjectile->src_y / 16));
|
|
}
|
|
if (curProjectile->x/16 >= getViewCurrentX() && curProjectile->x/16 < getViewCurrentX() + HORIZONTAL_WIDTH && curProjectile->y/16 >= getViewCurrentY() && curProjectile->y/16 < getViewCurrentY() + HORIZONTAL_HEIGHT)
|
|
playSoundeffect(SE_IMPACT_UNIT);
|
|
}
|
|
}
|
|
curProjectile->enabled = 0;
|
|
} else if (environmentLayout->contains_structure != -1) { // might have hit a structure
|
|
structureId = environmentLayout->contains_structure;
|
|
if (structureId < -1) // it was just a reference to a structure ID
|
|
structureId = (environmentLayout + (structureId + 1))->contains_structure;
|
|
if (curProjectileInfo->type >= PT_ROCKET || !structureInfo[structure[structureId].info].foundation) { // hit a structure
|
|
if (curProjectileInfo->type < PT_ROCKET) {
|
|
int originStructure = environment.layout[TILE_FROM_XY(curProjectile->src_x / 16, curProjectile->src_y / 16)].contains_structure;
|
|
if (originStructure < -1)
|
|
originStructure = environment.layout[TILE_FROM_XY(curProjectile->src_x / 16, curProjectile->src_y / 16) + (originStructure + 1)].contains_structure;
|
|
if (originStructure >= 0 && structure[originStructure].side == structure[structureId].side)
|
|
return;
|
|
}
|
|
if (curProjectileInfo->type >= PT_SHELL && curProjectileInfo->explosion_radius <= MAX_RADIUS_PROJECTILE_FOR_SINGLE_STRUCTURE_HIT) {
|
|
for (j=0; j<projectileStructureHitAmount && projectileStructureHit[j] != structureId; j++);
|
|
if (j < projectileStructureHitAmount)
|
|
return;
|
|
projectileStructureHit[projectileStructureHitAmount] = structureId;
|
|
projectileStructureHitAmount++;
|
|
}
|
|
if (structure[structureId].armour > 0 && structure[structureId].armour != INFINITE_ARMOUR) {
|
|
damage = curProjectileInfo->power;
|
|
effectModifier = curProjectileInfo->effectModifier[!structure[structureId].side != !curProjectile->side];
|
|
if (effectModifier == EMT_NORMAL)
|
|
effectModifier = curProjectileInfo->effectModifier[EME_STRUCTURES];
|
|
switch (effectModifier) {
|
|
case EMT_NORMAL:
|
|
break;
|
|
case EMT_NIL:
|
|
damage = 0;
|
|
break;
|
|
case EMT_DIMINISHED:
|
|
damage = (damage * 70) / 100;
|
|
break;
|
|
case EMT_INCREASED:
|
|
damage = (damage * 130) / 100;
|
|
break;
|
|
case EMT_INSTANT:
|
|
damage *= structureInfo[structure[structureId].info].max_armour;
|
|
}
|
|
structure[structureId].armour -= damage;
|
|
if (curProjectileInfo->power < 0) { // healing effect
|
|
if (structure[structureId].armour > structureInfo[structure[structureId].info].max_armour)
|
|
structure[structureId].armour = structureInfo[structure[structureId].info].max_armour;
|
|
if (structure[structureId].armour >= structureInfo[structure[structureId].info].max_armour / 2)
|
|
structure[structureId].smoke_time = 0;
|
|
} else { // destroying effect
|
|
|
|
displayHitRadar(curProjectile->x / 16, curProjectile->y / 16);
|
|
|
|
if (structureInfo[structure[structureId].info].foundation) { // a hack for foundations, used for optimization. not as pretty but necessary.
|
|
if (structure[structureId].armour <= 0) {
|
|
// any unit attacking this structure should stop
|
|
for (j=0; j<MAX_UNITS_ON_MAP; j++) {
|
|
if (unit[j].enabled && (unit[j].logic == UL_ATTACK_LOCATION || unit[j].logic == UL_ATTACK_AREA) && unit[j].logic_aid == tile) {
|
|
unit[j].logic = UL_GUARD;
|
|
if (getGameType() == SINGLEPLAYER) {
|
|
if (unit[j].side != FRIENDLY) {
|
|
if (unit[j].group == UGAI_HUNT) {
|
|
unit[j].logic = UL_HUNT;
|
|
unit[j].logic_aid = 0;
|
|
} else if (unit[j].group == UGAI_KAMIKAZE)
|
|
unit[j].logic = UL_KAMIKAZE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// remove it from map
|
|
environment.layout[tile].contains_structure = -1;
|
|
setTileDirtyRadarDirtyBitmap(tile);
|
|
addExplosion(curProjectile->x / 16, curProjectile->y / 16, structureInfo[structure[structureId].info].destroyed_explosion_info, 0);
|
|
// set it back to existing again for reuse by other locations
|
|
structure[structureId].armour = 1;
|
|
}
|
|
} else {
|
|
if (!structureInfo[structure[structureId].info].barrier) { // not foundation, not barrier
|
|
if (structure[structureId].armour < structureInfo[structure[structureId].info].max_armour / 2)
|
|
structure[structureId].smoke_time = STRUCTURE_SMOKE_DURATION + (structure[structureId].smoke_time % (3 * STRUCTURE_SINGLE_SMOKE_DURATION));
|
|
}
|
|
doStructureLogicHit(structureId, TILE_FROM_XY(curProjectile->src_x / 16, curProjectile->src_y / 16));
|
|
if (curProjectile->x/16 >= getViewCurrentX() && curProjectile->x/16 < getViewCurrentX() + HORIZONTAL_WIDTH && curProjectile->y/16 >= getViewCurrentY() && curProjectile->y/16 < getViewCurrentY() + HORIZONTAL_HEIGHT)
|
|
playSoundeffect(SE_IMPACT_STRUCTURE);
|
|
}
|
|
}
|
|
}
|
|
curProjectile->enabled = 0;
|
|
}
|
|
} else { // maybe sand or rock was hit?
|
|
/* if (environmentLayout->graphics >= ROCKCHASM && environmentLayout->graphics <= ROCKCHASM16)
|
|
curProjectile->enabled = 0;
|
|
else*/
|
|
if (curProjectileInfo->type == PT_ROCKET || curProjectileInfo->type == PT_AERIAL_ROCKET) {
|
|
if (/*environmentLayout->graphics >= SAND &&*/ environmentLayout->graphics <= SANDHILL16 || (environmentLayout->graphics >= ORE && environmentLayout->graphics <= OREHILL16))
|
|
addOverlay(curProjectile->x / 16, curProjectile->y / 16, OT_SAND_SHOT, 0);
|
|
else if (environmentLayout->graphics >= ROCK && environmentLayout->graphics <= ROCKCUSTOM32)
|
|
addOverlay(curProjectile->x / 16, curProjectile->y / 16, OT_ROCK_SHOT, 0);
|
|
else if ((environmentLayout->graphics >= SANDCHASM && environmentLayout->graphics <= SANDCHASM16) || (environmentLayout->graphics >= ROCKCHASM && environmentLayout->graphics <= ROCKCHASM16) || environmentLayout->graphics >= CHASMCUSTOM)
|
|
playSoundeffectControlled(SE_IMPACT_SAND, volumePercentageOfLocation(curProjectile->x / 16, curProjectile->y / 16, DISTANCE_TO_HALVE_SE_IMPACT_SAND), soundeffectPanningOfLocation(curProjectile->x / 16, curProjectile->y / 16));
|
|
curProjectile->enabled = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void setProjectileImpassableOverEnvironment(unsigned graphics, unsigned int impassable) {
|
|
if (impassable)
|
|
projectileImpassableOverEnvironment[graphics / 32] |= BIT(graphics % 32);
|
|
else
|
|
projectileImpassableOverEnvironment[graphics / 32] &= (~BIT(graphics % 32));
|
|
}
|
|
|
|
|
|
inline unsigned int getProjectileImpassableOverEnvironment(enum EnvironmentTileGraphics graphics) {
|
|
return projectileImpassableOverEnvironment[graphics / 32] & BIT(graphics % 32);
|
|
}
|
|
|
|
|
|
|
|
int isClearPathProjectile(int curX, int curY, int projectile_info, int tgtX, int tgtY) {
|
|
// no projectile never affected code before, shouldn't now either. stating it as clear path.
|
|
if (projectile_info <= -1)
|
|
return 1;
|
|
|
|
// aerial projectiles always have a clear path
|
|
struct ProjectileInfo *curProjectileInfo = projectileInfo + projectile_info;
|
|
if (curProjectileInfo->type & 1)
|
|
return 1;
|
|
|
|
int absDX = abs(tgtX - curX) * 16;
|
|
int absDY = abs(tgtY - curY) * 16;
|
|
|
|
int timer;
|
|
int time_required = square_root(absDX*absDX + absDY*absDY) / curProjectileInfo->speed;
|
|
|
|
struct EnvironmentLayout *envTile;
|
|
int done;
|
|
int step;
|
|
int x, y;
|
|
int xdiff, ydiff;
|
|
int structureId;
|
|
|
|
envTile = environment.layout + TILE_FROM_XY(tgtX, tgtY);
|
|
int targetStructure = envTile->contains_structure;
|
|
if (targetStructure < -1)
|
|
targetStructure = (envTile + (targetStructure + 1))->contains_structure;
|
|
|
|
envTile = environment.layout + TILE_FROM_XY(curX, curY);
|
|
enum Side side;
|
|
int originStructure = envTile->contains_unit < 0; // used as boolean here
|
|
if (!originStructure)
|
|
side = unit[envTile->contains_unit].side;
|
|
else {
|
|
if (envTile->contains_structure < -1)
|
|
side = structure[(envTile + (envTile->contains_structure + 1))->contains_structure].side;
|
|
else
|
|
side = structure[envTile->contains_structure].side;
|
|
}
|
|
|
|
curX = curX*16 + 8;
|
|
curY = curY*16 + 8;
|
|
tgtX = tgtX*16 + 8;
|
|
tgtY = tgtY*16 + 8;
|
|
|
|
for (timer=1; timer<time_required; timer++) {
|
|
|
|
step = 1; // multiple steps might be needed to have proper colission detection on each tile
|
|
do {
|
|
done = 1; // assuming the current (colission) step will suffice for the current projectile
|
|
xdiff = ((tgtX - curX) * timer) / time_required;
|
|
ydiff = ((tgtY - curY) * timer) / time_required;
|
|
if (abs(xdiff) > abs(ydiff)) {
|
|
if (abs(xdiff) > step * PROJECTILE_MAXIMUM_COLISSION_STEP) {
|
|
if (ydiff >= 0)
|
|
ydiff = (ydiff * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(xdiff);
|
|
else
|
|
ydiff = - ((abs(ydiff) * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(xdiff));
|
|
if (xdiff >= 0)
|
|
xdiff = step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
else
|
|
xdiff = - step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
done = 0;
|
|
}
|
|
} else {
|
|
if (abs(ydiff) > step * PROJECTILE_MAXIMUM_COLISSION_STEP) {
|
|
if (xdiff >= 0)
|
|
xdiff = (xdiff * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(ydiff);
|
|
else
|
|
xdiff = - ((abs(xdiff) * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(ydiff));
|
|
if (ydiff >= 0)
|
|
ydiff = step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
else
|
|
ydiff = - step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
done = 0;
|
|
}
|
|
}
|
|
x = curX + xdiff;
|
|
y = curY + ydiff;
|
|
|
|
if (x/16 == tgtX/16 && y/16 == tgtY/16)
|
|
return 1;
|
|
|
|
if (timer >= time_required && done)
|
|
return 1;
|
|
|
|
envTile = environment.layout + TILE_FROM_XY(x/16, y/16);
|
|
if (getProjectileImpassableOverEnvironment(envTile->graphics))
|
|
return 0;
|
|
|
|
if (targetStructure >= MAX_DIFFERENT_FACTIONS) { // target structure exists and is not foundation
|
|
if (envTile->contains_structure == targetStructure)
|
|
return 1;
|
|
if (envTile->contains_structure < -1 &&
|
|
(envTile + envTile->contains_structure + 1)->contains_structure == targetStructure)
|
|
return 1;
|
|
}
|
|
|
|
if ((curProjectileInfo->type == PT_SHOT || curProjectileInfo->type == PT_BULLET) &&
|
|
(envTile->contains_unit <= -1 || (!unit[envTile->contains_unit].side != !side))) {
|
|
|
|
if (envTile->contains_unit >= 0) // hit an enemy unit, we're fine with that. considered clear.
|
|
return 1;
|
|
|
|
if (envTile->contains_structure != -1) { // might have hit a structure
|
|
structureId = envTile->contains_structure;
|
|
if (structureId < -1) // it was just a reference to a structure ID
|
|
structureId = (envTile + (structureId + 1))->contains_structure;
|
|
if (curProjectileInfo->type >= PT_ROCKET || !structureInfo[structure[structureId].info].foundation) { // hit a structure
|
|
if (curProjectileInfo->type < PT_ROCKET) {
|
|
int originStructure = environment.layout[TILE_FROM_XY(curX/16, curY/16)].contains_structure;
|
|
if (originStructure < -1)
|
|
originStructure = environment.layout[TILE_FROM_XY(curX/16 + (originStructure + 1), curY/16)].contains_structure;
|
|
if (!(originStructure >= 0 && structure[originStructure].side == structure[structureId].side))
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
step++;
|
|
} while (!done);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
void doProjectilesLogic() {
|
|
int i, j, k;
|
|
int x, y;
|
|
int xdiff, ydiff;
|
|
int maxsquared;
|
|
int done, step;
|
|
enum EnvironmentTileGraphics environmentTileGraphics;
|
|
struct Projectile *curProjectile = projectile;
|
|
struct ProjectileInfo *curProjectileInfo;
|
|
|
|
startProfilingFunction("doProjectilesLogic");
|
|
|
|
for (i=0; i<MAX_PROJECTILES_ON_MAP; i++, curProjectile++) {
|
|
if (curProjectile->enabled) {
|
|
projectileStructureHitAmount = 0;
|
|
curProjectileInfo = projectileInfo + curProjectile->info;
|
|
if (curProjectile->timer < curProjectile->time_required)
|
|
curProjectile->timer++;
|
|
step = 1; // multiple steps might be needed to have proper colission detection on each tile
|
|
do {
|
|
done = 1; // assuming the current (colission) step will suffice for the current projectile
|
|
xdiff = ((curProjectile->tgt_x - curProjectile->src_x) * curProjectile->timer) / curProjectile->time_required;
|
|
ydiff = ((curProjectile->tgt_y - curProjectile->src_y) * curProjectile->timer) / curProjectile->time_required;
|
|
if (abs(xdiff) > abs(ydiff)) {
|
|
if (abs(xdiff) > step * PROJECTILE_MAXIMUM_COLISSION_STEP) {
|
|
if (ydiff >= 0)
|
|
ydiff = (ydiff * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(xdiff);
|
|
else
|
|
ydiff = - ((abs(ydiff) * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(xdiff));
|
|
if (xdiff >= 0)
|
|
xdiff = step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
else
|
|
xdiff = - step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
done = 0;
|
|
}
|
|
} else {
|
|
if (abs(ydiff) > step * PROJECTILE_MAXIMUM_COLISSION_STEP) {
|
|
if (xdiff >= 0)
|
|
xdiff = (xdiff * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(ydiff);
|
|
else
|
|
xdiff = - ((abs(xdiff) * (step * PROJECTILE_MAXIMUM_COLISSION_STEP)) / abs(ydiff));
|
|
if (ydiff >= 0)
|
|
ydiff = step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
else
|
|
ydiff = - step * PROJECTILE_MAXIMUM_COLISSION_STEP;
|
|
done = 0;
|
|
}
|
|
}
|
|
curProjectile->x = curProjectile->src_x + xdiff;
|
|
curProjectile->y = curProjectile->src_y + ydiff;
|
|
|
|
environmentTileGraphics = environment.layout[TILE_FROM_XY(curProjectile->x / 16, curProjectile->y / 16)].graphics;
|
|
if ((curProjectile->timer >= curProjectile->time_required && done) || (!(curProjectileInfo->type & 1) && getProjectileImpassableOverEnvironment(environmentTileGraphics))) {
|
|
// projectile reached its target position
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, 0);
|
|
if (curProjectileInfo->type >= PT_SHELL) {
|
|
x = curProjectile->x;
|
|
y = curProjectile->y;
|
|
for (k=1; k<=curProjectileInfo->explosion_radius; k++) {
|
|
if ((curProjectile->x = x - (k*16)) > 0) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
if ((curProjectile->x = x + (k*16)) < environment.width * 16) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
}
|
|
for (j=1; j<=curProjectileInfo->explosion_radius; j++) {
|
|
if ((curProjectile->y = y - (j*16)) > 0) {
|
|
curProjectile->x = x;
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
maxsquared = curProjectileInfo->explosion_radius*curProjectileInfo->explosion_radius - j*j;
|
|
for (k=1; k*k <= maxsquared; k++) {
|
|
if ((curProjectile->x = x - (k*16)) > 0) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
if ((curProjectile->x = x + (k*16)) < environment.width * 16) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
}
|
|
}
|
|
if ((curProjectile->y = y + (j*16)) < environment.height * 16) {
|
|
curProjectile->x = x;
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
maxsquared = curProjectileInfo->explosion_radius*curProjectileInfo->explosion_radius - j*j;
|
|
for (k=1; k*k <= maxsquared; k++) {
|
|
if ((curProjectile->x = x - (k*16)) > 0) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
if ((curProjectile->x = x + (k*16)) < environment.width * 16) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, rand()%6);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
curProjectile->enabled = 0;
|
|
done = 1;
|
|
} else if ((curProjectileInfo->type == PT_SHOT || curProjectileInfo->type == PT_BULLET) && (abs(curProjectile->x - curProjectile->src_x) >= 16 || abs(curProjectile->y - curProjectile->src_y) >= 16) &&
|
|
(environment.layout[TILE_FROM_XY(curProjectile->x/16, curProjectile->y/16)].contains_unit <= -1 || (!unit[environment.layout[TILE_FROM_XY(curProjectile->x/16, curProjectile->y/16)].contains_unit].side != !curProjectile->side))) {
|
|
doProjectileImpact(curProjectile, curProjectileInfo, structure, structureInfo, unit, unitInfo);
|
|
if (!curProjectile->enabled) { // it hit something
|
|
addExplosion(curProjectile->x, curProjectile->y, curProjectileInfo->explosion_info, 0);
|
|
done = 1;
|
|
}
|
|
}
|
|
|
|
step++;
|
|
} while (!done);
|
|
}
|
|
}
|
|
|
|
stopProfilingFunction();
|
|
}
|
|
|
|
void loadProjectilesGraphicsBG(int baseBg, int *offsetBg) {
|
|
}
|
|
|
|
void loadProjectilesGraphicsSprites(int *offsetSp) {
|
|
int i;
|
|
|
|
for (i=0; i<MAX_DIFFERENT_PROJECTILES && projectileInfo[i].enabled; i++) {
|
|
projectileInfo[i].graphics_offset = (*offsetSp)/(16*16);
|
|
*offsetSp += copyFileVRAM(SPRITE_EXPANDED_GFX + ((*offsetSp)>>1), projectileInfo[i].name, FS_PROJECTILES_GRAPHICS);
|
|
}
|
|
}
|
|
|
|
|
|
int getProjectilesSaveSize(void) {
|
|
return sizeof(projectile);
|
|
}
|
|
|
|
int getProjectilesSaveData(void *dest, int max_size) {
|
|
int size=sizeof(projectile);
|
|
|
|
if (size>max_size)
|
|
return(0);
|
|
|
|
memcpy (dest,&projectile,size);
|
|
return (size);
|
|
}
|