/** ** Work for missile hit. */ global void MissileHit(int missile) { Unit* goal; // FIXME: should I move the PlayMissileSound to here? // FIXME: And should the the connected missile be defined in the missile // FIXME: structure switch( Missiles[missile].Type->Type ) { case MissileArrow: case MissileAxe: PlayMissileSound(Missiles+missile, Missiles[missile].Type->ImpactSound.Sound); break; case MissileBallistaBolt: case MissileBigCannon: PlayMissileSound(Missiles+missile, Missiles[missile].Type->ImpactSound.Sound); MakeMissile(MissileImpact ,Missiles[missile].X ,Missiles[missile].Y ,0,0); break; case MissileSubmarineMissile: case MissileTurtleMissile: PlayMissileSound(Missiles+missile, Missiles[missile].Type->ImpactSound.Sound); MakeMissile(MissileImpact ,Missiles[missile].X ,Missiles[missile].Y ,0,0); break; case MissileGreenCross: break; } if( !Missiles[missile].SourceType ) { return; } // FIXME: must choose better goal! // FIXME: what can the missile hit? goal=UnitOnMapTile(Missiles[missile].X/TileSizeX ,Missiles[missile].Y/TileSizeY); if( !goal || !goal->HP ) { return; } HitUnit(goal,CalculateDamage(Missiles[missile].SourceStats,goal)); }
/** ** Handle point to point missile. ** ** @param missile Missile pointer. ** ** @return true if goal is reached, false else. */ bool PointToPointMissile(Missile &missile) { if (MissileInitMove(missile) == true) { return true; } Assert(missile.Type != NULL); Assert(missile.TotalStep != 0); const PixelPos diff = (missile.destination - missile.source); missile.position = missile.source + diff * missile.CurrentStep / missile.TotalStep; if (missile.Type->Smoke.Missile && (missile.CurrentStep || missile.State > 1)) { const PixelPos position = missile.position + missile.Type->size / 2; MakeMissile(*missile.Type->Smoke.Missile, position, position); } if (missile.Type->SmokeParticle && (missile.CurrentStep || missile.State > 1)) { const PixelPos position = missile.position + missile.Type->size / 2; missile.Type->SmokeParticle->pushPreamble(); missile.Type->SmokeParticle->pushInteger(position.x); missile.Type->SmokeParticle->pushInteger(position.y); missile.Type->SmokeParticle->run(); } if (missile.Type->Pierce) { MissileHandlePierce(missile, Map.MapPixelPosToTilePos(missile.position)); } return false; }
/** ** Fire missile. */ global void FireMissile(Unit* unit) { int x; int y; int dx; int dy; Unit* goal; Missile* missile; DebugLevel3("%s:\n",__FUNCTION__); if( unit->Type->MissileWeapon==MissileNone ) { // FIXME: must hit now!!! if( !(goal=unit->Command.Data.Move.Goal) ) { DebugLevel1("Missile-none hits no unit, shouldn't happen!\n"); return; } // FIXME: make sure thats the correct unit. if( !goal->HP || goal->Command.Action==UnitActionDie ) { return; } HitUnit(goal,CalculateDamage(unit->Stats,goal)); return; } x=unit->X*TileSizeX+TileSizeX/2; y=unit->Y*TileSizeY+TileSizeY/2; if( (goal=unit->Command.Data.Move.Goal) ) { // Fire to nearest point of unit! if( goal->Type ) { NearestOfUnit(goal,unit->X,unit->Y,&dx,&dy); } else { // FIXME: unit killed? dx=goal->X; dy=goal->Y; } DebugLevel3("Fire to unit at %d,%d\n",dx,dy); dx=dx*TileSizeX+TileSizeX/2; dy=dy*TileSizeY+TileSizeY/2; } else { dx=unit->Command.Data.Move.DX*TileSizeX+TileSizeX/2; dy=unit->Command.Data.Move.DY*TileSizeY+TileSizeY/2; } missile=MakeMissile(unit->Type->MissileWeapon,x,y,dx,dy); // // Damage of missile // // missile->Damage=CalculateDamage(unit,goal); missile->SourceUnit=unit; missile->SourceType=unit->Type; missile->SourceStats=unit->Stats; missile->SourcePlayer=unit->Player; }
/** ** Create a missile on the map ** ** @param l Lua state. ** */ static int CclCreateMissile(lua_State *l) { LuaCheckArgs(l, 6); const std::string name = LuaToString(l, 1); const MissileType *mtype = MissileTypeByIdent(name); if (!mtype) { LuaError(l, "Bad missile"); } PixelPos startpos, endpos; if (!lua_istable(l, 2) || lua_rawlen(l, 2) != 2) { LuaError(l, "incorrect argument !!"); } lua_rawgeti(l, 2, 1); startpos.x = LuaToNumber(l, -1); lua_pop(l, 1); lua_rawgeti(l, 2, 2); startpos.y = LuaToNumber(l, -1); lua_pop(l, 1); if (!lua_istable(l, 3) || lua_rawlen(l, 3) != 2) { LuaError(l, "incorrect argument !!"); } lua_rawgeti(l, 3, 1); endpos.x = LuaToNumber(l, -1); lua_pop(l, 1); lua_rawgeti(l, 3, 2); endpos.y = LuaToNumber(l, -1); lua_pop(l, 1); const int sourceUnitId = LuaToNumber(l, 4); const int destUnitId = LuaToNumber(l, 5); const bool dealDamage = LuaToBoolean(l, 6); CUnit *sourceUnit = sourceUnitId != -1 ? &UnitManager.GetSlotUnit(sourceUnitId) : NULL; CUnit *destUnit = destUnitId != -1 ? &UnitManager.GetSlotUnit(destUnitId) : NULL; if (sourceUnit != NULL) { startpos += sourceUnit->GetMapPixelPosTopLeft(); } if (destUnit != NULL) { endpos += destUnit->GetMapPixelPosTopLeft(); } Missile *missile = MakeMissile(*mtype, startpos, endpos); if (!missile) { return 0; } if (dealDamage) { missile->SourceUnit = sourceUnit; } missile->TargetUnit = destUnit; return 0; }
/** ** Create a missile on the map ** ** @param l Lua state. ** */ static int CclCreateMissile(lua_State *l) { const int arg = lua_gettop(l); if (arg < 6 || arg > 7) { LuaError(l, "incorrect argument"); } const std::string name = LuaToString(l, 1); const MissileType *mtype = MissileTypeByIdent(name); if (!mtype) { LuaError(l, "Bad missile"); } PixelPos startpos, endpos; CclGetPos(l, &startpos.x, &startpos.y, 2); CclGetPos(l, &endpos.x, &endpos.y, 3); const int sourceUnitId = LuaToNumber(l, 4); const int destUnitId = LuaToNumber(l, 5); const bool dealDamage = LuaToBoolean(l, 6); const bool mapRelative = arg == 7 ? LuaToBoolean(l, 7) : false; CUnit *sourceUnit = sourceUnitId != -1 ? &UnitManager.GetSlotUnit(sourceUnitId) : nullptr; CUnit *destUnit = destUnitId != -1 ? &UnitManager.GetSlotUnit(destUnitId) : nullptr; if (mapRelative == false) { if (sourceUnit != nullptr) { startpos += sourceUnit->GetMapPixelPosTopLeft(); } if (destUnit != nullptr) { endpos += destUnit->GetMapPixelPosTopLeft(); } } //Wyrmgus start // Missile *missile = MakeMissile(*mtype, startpos, endpos); Missile *missile = MakeMissile(*mtype, startpos, endpos, 0); //Wyrmgus end if (!missile) { return 0; } if (dealDamage) { missile->SourceUnit = sourceUnit; } missile->TargetUnit = destUnit; return 0; }
//Wyrmgus start ///* virtual */ int Spell_AreaBombardment::Cast(CUnit &caster, const SpellType &, CUnit *, const Vec2i &goalPos) /* virtual */ int Spell_AreaBombardment::Cast(CUnit &caster, const SpellType &, CUnit *, const Vec2i &goalPos, int z) //Wyrmgus end { int fields = this->Fields; const int shards = this->Shards; const int damage = this->Damage; const PixelDiff offset(this->StartOffsetX, this->StartOffsetY); const MissileType *missile = this->Missile; while (fields--) { Vec2i dpos; // FIXME: radius configurable... do { // find new destination in the map dpos.x = goalPos.x + SyncRand() % 5 - 2; dpos.y = goalPos.y + SyncRand() % 5 - 2; //Wyrmgus start // } while (!Map.Info.IsPointOnMap(dpos)); } while (!Map.Info.IsPointOnMap(dpos, z)); //Wyrmgus end const PixelPos dest = Map.TilePosToMapPixelPos_Center(dpos); const PixelPos start = dest + offset; for (int i = 0; i < shards; ++i) { //Wyrmgus start // ::Missile *mis = MakeMissile(*missile, start, dest); ::Missile *mis = MakeMissile(*missile, start, dest, z); //Wyrmgus end if (mis->Type->BlizzardSpeed) { mis->Delay = i * mis->Type->Sleep * 2 * PixelTileSize.x / mis->Type->BlizzardSpeed; } else if (mis->Type->Speed) { mis->Delay = i * mis->Type->Sleep * 2 * PixelTileSize.x / mis->Type->Speed; } else { mis->Delay = i * mis->Type->Sleep * mis->Type->G->NumFrames; } mis->Damage = damage; // FIXME: not correct -- blizzard should continue even if mage is // destroyed (though it will be quite short time...) mis->SourceUnit = &caster; } } return 1; }
/** ** Calculate parabolic trajectories. ** ** @param missile Missile pointer. ** ** @return 1 if target is reached, 0 otherwise ** ** @todo Find good values for ZprojToX and Y */ static int ParabolicMissile(Missile *missile) { int orig_x; // position before moving. int orig_y; // position before moving. int xstep; int ystep; int k; // Coefficient of the parabol. int zprojToX; // Projection of Z axis on axis X. int zprojToY; // Projection of Z axis on axis Y. int z; // should be missile->Z later. int x; int y; k = -2048; //-1024; // Should be initialised by an other method (computed with distance...) zprojToX = 4; zprojToY = 1024; if (MissileInitMove(missile) == 1) { return 1; } Assert(missile->Type != NULL); orig_x = missile->X; orig_y = missile->Y; xstep = missile->DX - missile->SourceX; ystep = missile->DY - missile->SourceY; Assert(missile->TotalStep != 0); xstep = xstep * 1000 / missile->TotalStep; ystep = ystep * 1000 / missile->TotalStep; missile->X = missile->SourceX + xstep * missile->CurrentStep / 1000; missile->Y = missile->SourceY + ystep * missile->CurrentStep / 1000; Assert(k != 0); z = missile->CurrentStep * (missile->TotalStep - missile->CurrentStep) / k; // Until Z is used for drawing, modify X and Y. missile->X += z * zprojToX / 64; missile->Y += z * zprojToY / 64; MissileNewHeadingFromXY(missile, missile->X - orig_x, missile->Y - orig_y); if (missile->Type->SmokeMissile && missile->CurrentStep) { x = missile->X + missile->Type->Width / 2; y = missile->Y + missile->Type->Height / 2; MakeMissile(missile->Type->SmokeMissile, x, y, x, y); } return 0; }
/** ** Calculate parabolic trajectories. ** ** @param missile Missile pointer. ** ** @return 1 if target is reached, 0 otherwise ** ** @todo Find good values for ZprojToX and Y */ static int ParabolicMissile(Missile &missile) { int k; // Coefficient of the parabol. int zprojToX; // Projection of Z axis on axis X. int zprojToY; // Projection of Z axis on axis Y. int z; // should be missile.Z later. k = -2048; //-1024; // Should be initialised by an other method (computed with distance...) zprojToX = 4; zprojToY = 1024; if (MissileInitMove(missile) == 1) { return 1; } Assert(missile.Type != NULL); const PixelPos orig_pos = missile.position; Assert(missile.TotalStep != 0); const PixelPos diff = (missile.destination - missile.source); missile.position = missile.source + diff * missile.CurrentStep / missile.TotalStep; Assert(k != 0); z = missile.CurrentStep * (missile.TotalStep - missile.CurrentStep) / k; // Until Z is used for drawing, modify X and Y. missile.position.x += z * zprojToX / 64; missile.position.y += z * zprojToY / 64; missile.MissileNewHeadingFromXY(missile.position - orig_pos); if (missile.Type->Smoke.Missile && missile.CurrentStep) { const PixelPos position = missile.position + missile.Type->size / 2; MakeMissile(*missile.Type->Smoke.Missile, position, position); } if (missile.Type->SmokeParticle && missile.CurrentStep) { const PixelPos position = missile.position + missile.Type->size / 2; missile.Type->SmokeParticle->pushPreamble(); missile.Type->SmokeParticle->pushInteger(position.x); missile.Type->SmokeParticle->pushInteger(position.y); missile.Type->SmokeParticle->run(); } if (missile.Type->Pierce) { MissileHandlePierce(missile, Map.MapPixelPosToTilePos(missile.position)); } return 0; }
/** ** Handle point to point missile. ** ** @param missile Missile pointer. ** ** @return 1 if goal is reached, 0 else. */ static int PointToPointMissile(Missile *missile) { int xstep; int ystep; int x; int y; if (MissileInitMove(missile) == 1) { return 1; } Assert(missile->Type != NULL); Assert(missile->TotalStep != 0); xstep = (missile->DX - missile->SourceX) * 1024 / missile->TotalStep; ystep = (missile->DY - missile->SourceY) * 1024 / missile->TotalStep; missile->X = missile->SourceX + xstep * missile->CurrentStep / 1024; missile->Y = missile->SourceY + ystep * missile->CurrentStep / 1024; if (missile->Type->SmokeMissile && missile->CurrentStep) { x = missile->X + missile->Type->Width / 2; y = missile->Y + missile->Type->Height / 2; MakeMissile(missile->Type->SmokeMissile, x, y, x, y); } return 0; }
/** ** Handle point to point missile. ** ** @param missile Missile pointer. ** ** @return true if goal is reached, false else. */ bool PointToPointMissile(Missile &missile) { MissileInitMove(missile); if (missile.TotalStep == 0) { return true; } Assert(missile.Type != NULL); Assert(missile.TotalStep != 0); const PixelPos diff = (missile.destination - missile.source); const PixelPrecise sign(diff.x >= 0 ? 1.0 : -1.0, diff.y >= 0 ? 1.0 : -1.0); // Remember sign to move into correct direction const PixelPrecise oldPos((double)missile.position.x, (double)missile.position.y); // Remember old position PixelPrecise pos(oldPos); missile.position = missile.source + diff * missile.CurrentStep / missile.TotalStep; for (; pos.x * sign.x <= missile.position.x * sign.x && pos.y * sign.y <= missile.position.y * sign.y; pos.x += (double)diff.x * missile.Type->SmokePrecision / missile.TotalStep, pos.y += (double)diff.y * missile.Type->SmokePrecision / missile.TotalStep) { const PixelPos position((int)pos.x + missile.Type->size.x / 2, (int)pos.y + missile.Type->size.y / 2); if (missile.Type->Smoke.Missile && (missile.CurrentStep || missile.State > 1)) { Missile *smoke = MakeMissile(*missile.Type->Smoke.Missile, position, position); if (smoke && smoke->Type->NumDirections > 1) { smoke->MissileNewHeadingFromXY(diff); } } if (missile.Type->SmokeParticle && (missile.CurrentStep || missile.State > 1)) { missile.Type->SmokeParticle->pushPreamble(); missile.Type->SmokeParticle->pushInteger(position.x); missile.Type->SmokeParticle->pushInteger(position.y); missile.Type->SmokeParticle->run(); } if (missile.Type->Pierce) { const PixelPos posInt((int)pos.x, (int)pos.y); MissileHandlePierce(missile, Map.MapPixelPosToTilePos(posInt)); } } // Handle wall blocking and kill first enemy for (pos = oldPos; pos.x * sign.x <= missile.position.x * sign.x && pos.y * sign.y <= missile.position.y * sign.y; pos.x += (double)diff.x / missile.TotalStep, pos.y += (double)diff.y / missile.TotalStep) { const PixelPos position((int)pos.x + missile.Type->size.x / 2, (int)pos.y + missile.Type->size.y / 2); const Vec2i tilePos(Map.MapPixelPosToTilePos(position)); if (Map.Info.IsPointOnMap(tilePos) && MissileHandleBlocking(missile, position)) { return true; } if (missile.Type->MissileStopFlags) { if (!Map.Info.IsPointOnMap(tilePos)) { // gone outside missile.TTL = 0; return false; } const CMapField &mf = *Map.Field(tilePos); if (missile.Type->MissileStopFlags & mf.Flags) { // incompatible terrain missile.position = position; missile.MissileHit(); missile.TTL = 0; return false; } } } if (missile.CurrentStep == missile.TotalStep) { missile.position = missile.destination; return true; } return false; }
/** ** Create a missile. ** ** @param l Lua state. */ static int CclMissile(lua_State *l) { MissileType *type = nullptr; PixelPos position(-1, -1); PixelPos destination(-1, -1); PixelPos source(-1, -1); int z = 0; Missile *missile = nullptr; DebugPrint("FIXME: not finished\n"); const int args = lua_gettop(l); for (int j = 0; j < args; ++j) { const char *value = LuaToString(l, j + 1); ++j; if (!strcmp(value, "type")) { type = MissileTypeByIdent(LuaToString(l, j + 1)); } else if (!strcmp(value, "pos")) { CclGetPos(l, &position.x, &position.y, j + 1); } else if (!strcmp(value, "origin-pos")) { CclGetPos(l, &source.x, &source.y, j + 1); } else if (!strcmp(value, "goal")) { CclGetPos(l, &destination.x, &destination.y, j + 1); //Wyrmgus start } else if (!strcmp(value, "map-layer")) { z = LuaToNumber(l, j + 1); //Wyrmgus end } else if (!strcmp(value, "local")) { Assert(type); //Wyrmgus start // missile = MakeLocalMissile(*type, position, destination); missile = MakeLocalMissile(*type, position, destination, z); //Wyrmgus end missile->Local = 1; --j; } else if (!strcmp(value, "global")) { Assert(type); //Wyrmgus start // missile = MakeMissile(*type, position, destination); missile = MakeMissile(*type, position, destination, z); //Wyrmgus end missile->position = position; missile->source = source; missile->destination = destination; //Wyrmgus start missile->MapLayer = z; //Wyrmgus end missile->Local = 0; --j; } else if (!strcmp(value, "frame")) { Assert(missile); missile->SpriteFrame = LuaToNumber(l, j + 1); } else if (!strcmp(value, "state")) { Assert(missile); missile->State = LuaToNumber(l, j + 1); } else if (!strcmp(value, "anim-wait")) { Assert(missile); missile->AnimWait = LuaToNumber(l, j + 1); } else if (!strcmp(value, "wait")) { Assert(missile); missile->Wait = LuaToNumber(l, j + 1); } else if (!strcmp(value, "delay")) { Assert(missile); missile->Delay = LuaToNumber(l, j + 1); } else if (!strcmp(value, "source")) { Assert(missile); lua_pushvalue(l, j + 1); missile->SourceUnit = CclGetUnitFromRef(l); lua_pop(l, 1); } else if (!strcmp(value, "target")) { Assert(missile); lua_pushvalue(l, j + 1); missile->TargetUnit = CclGetUnitFromRef(l); lua_pop(l, 1); } else if (!strcmp(value, "damage")) { Assert(missile); missile->Damage = LuaToNumber(l, j + 1); } else if (!strcmp(value, "lightning-damage")) { Assert(missile); missile->LightningDamage = LuaToNumber(l, j + 1); } else if (!strcmp(value, "ttl")) { Assert(missile); missile->TTL = LuaToNumber(l, j + 1); } else if (!strcmp(value, "hidden")) { Assert(missile); missile->Hidden = 1; --j; } else if (!strcmp(value, "step")) { Assert(missile); if (!lua_istable(l, j + 1) || lua_rawlen(l, j + 1) != 2) { LuaError(l, "incorrect argument"); } missile->CurrentStep = LuaToNumber(l, j + 1, 1); missile->TotalStep = LuaToNumber(l, j + 1, 2); } else { LuaError(l, "Unsupported tag: %s" _C_ value); } } // we need to reinitialize position parameters - that's because of // the way InitMissile() (called from MakeLocalMissile()) computes // them - it works for creating a missile during a game but breaks // loading the missile from a file. missile->position = position; missile->source = source; missile->destination = destination; missile->MapLayer = z; return 0; }
/** ** Fire missile. ** ** @param unit Unit that fires the missile. */ void FireMissile(CUnit &unit, CUnit *goal, const Vec2i &goalPos) { Vec2i newgoalPos = goalPos; // Goal dead? if (goal) { Assert(!unit.Type->Missile.Missile->AlwaysFire || unit.Type->Missile.Missile->Range); if (goal->Destroyed) { DebugPrint("destroyed unit\n"); return; } if (goal->Removed) { return; } if (goal->CurrentAction() == UnitActionDie) { if (unit.Type->Missile.Missile->AlwaysFire) { newgoalPos = goal->tilePos; goal = NULL; } else { return; } } } // No missile hits immediately! //Wyrmgus start // if (unit.Type->Missile.Missile->Class == MissileClassNone) { if (unit.Type->Missile.Missile->Class == MissileClassNone || (unit.Type->Animations && unit.Type->Animations->Attack && unit.Type->Animations->RangedAttack && ((goal && unit.MapDistanceTo(*goal) <= 1) || (!goal && unit.MapDistanceTo(goalPos) <= 1)) && !unit.Container)) { // treat melee attacks from units that have both attack and ranged attack animations as having missile class none //Wyrmgus end //Wyrmgus start int damage = 0; //Wyrmgus end // No goal, take target coordinates if (!goal) { if (Map.WallOnMap(goalPos)) { //Wyrmgus start // if (Map.HumanWallOnMap(goalPos)) { if (Map.HumanWallOnMap(goalPos) && CalculateHit(unit, *UnitTypeHumanWall->Stats, NULL) == true) { //Wyrmgus end //Wyrmgus start PlayUnitSound(unit, VoiceHit); damage = CalculateDamageStats(unit, *UnitTypeHumanWall->Stats, NULL); //Wyrmgus end Map.HitWall(goalPos, //Wyrmgus start // CalculateDamageStats(*unit.Stats, // *UnitTypeHumanWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); damage); //Wyrmgus end //Wyrmgus start // } else { } else if (Map.OrcWallOnMap(goalPos) && CalculateHit(unit, *UnitTypeOrcWall->Stats, NULL) == true) { //Wyrmgus end //Wyrmgus start PlayUnitSound(unit, VoiceHit); damage = CalculateDamageStats(unit, *UnitTypeOrcWall->Stats, NULL); //Wyrmgus end Map.HitWall(goalPos, //Wyrmgus start // CalculateDamageStats(*unit.Stats, // *UnitTypeOrcWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); damage); //Wyrmgus end } return; } DebugPrint("Missile-none hits no unit, shouldn't happen!\n"); return; } //Wyrmgus start // HitUnit(&unit, *goal, CalculateDamage(unit, *goal, Damage)); if (CalculateHit(unit, *goal->Stats, goal) == true) { damage = CalculateDamage(unit, *goal, Damage); HitUnit(&unit, *goal, damage); PlayUnitSound(unit, VoiceHit); //apply Thorns damage if attacker is at melee range if (goal && goal->Variable[THORNSDAMAGE_INDEX].Value && unit.MapDistanceTo(*goal) <= 1) { int thorns_damage = std::max<int>(goal->Variable[THORNSDAMAGE_INDEX].Value - unit.Variable[ARMOR_INDEX].Value, 1); if (GameSettings.NoRandomness) { thorns_damage -= ((thorns_damage + 2) / 2) / 2; //if no randomness setting is used, then the damage will always return what would have been the average damage with randomness } else { thorns_damage -= SyncRand() % ((thorns_damage + 2) / 2); } HitUnit(goal, unit, thorns_damage); } } else { PlayUnitSound(unit, VoiceMiss); } //Wyrmgus end return; } // If Firing from inside a Bunker CUnit *from = GetFirstContainer(unit); const int dir = ((unit.Direction + NextDirection / 2) & 0xFF) / NextDirection; const PixelPos startPixelPos = Map.TilePosToMapPixelPos_Center(from->tilePos) + unit.Type->MissileOffsets[dir][0]; Vec2i dpos; if (goal) { Assert(goal->Type); // Target invalid? // Moved out of attack range? if (unit.MapDistanceTo(*goal) < unit.Type->MinAttackRange) { DebugPrint("Missile target too near %d,%d\n" _C_ unit.MapDistanceTo(*goal) _C_ unit.Type->MinAttackRange); // FIXME: do something other? return; } // Fire to nearest point of the unit! // If Firing from inside a Bunker if (unit.Container) { NearestOfUnit(*goal, GetFirstContainer(unit)->tilePos, &dpos); } else { dpos = goal->tilePos + goal->Type->GetHalfTileSize(); } } else { dpos = newgoalPos; // FIXME: Can this be too near?? } PixelPos destPixelPos = Map.TilePosToMapPixelPos_Center(dpos); Missile *missile = MakeMissile(*unit.Type->Missile.Missile, startPixelPos, destPixelPos); // // Damage of missile // if (goal) { missile->TargetUnit = goal; } missile->SourceUnit = &unit; }
/** ** Fire missile. ** ** @param unit Unit that fires the missile. */ void FireMissile(CUnit *unit) { int x; int y; int dx; int dy; CUnit *goal; Missile *missile; // // Goal dead? // goal = unit->Orders[0]->Goal; if (goal) { // Better let the caller/action handle this. if (goal->Destroyed) { DebugPrint("destroyed unit\n"); return; } if (goal->Removed || goal->Orders[0]->Action == UnitActionDie) { return; } // FIXME: Some missile hit the field of the target and all units on it. // FIXME: goal is already dead, but missile could hit others? } // // No missile hits immediately! // if (unit->Type->Missile.Missile->Class == MissileClassNone) { // No goal, take target coordinates if (!goal) { DebugPrint("Missile-none hits no unit, shouldn't happen!\n"); return; } HitUnit(unit, goal, CalculateDamage(unit, goal)); return; } // If Firing from inside a Bunker if (unit->Container) { x = unit->Container->X * TileSizeX + TileSizeX / 2; // missile starts in tile middle y = unit->Container->Y * TileSizeY + TileSizeY / 2; } else { x = unit->X * TileSizeX + TileSizeX / 2; // missile starts in tile middle y = unit->Y * TileSizeY + TileSizeY / 2; } if (goal) { Assert(goal->Type); // Target invalid? // // Moved out of attack range? // if (MapDistanceBetweenUnits(unit, goal) < unit->Type->MinAttackRange) { DebugPrint("Missile target too near %d,%d\n" _C_ MapDistanceBetweenUnits(unit, goal) _C_ unit->Type->MinAttackRange); // FIXME: do something other? return; } // Fire to nearest point of the unit! // If Firing from inside a Bunker if (unit->Container) { NearestOfUnit(goal, unit->Container->X, unit->Container->Y, &dx, &dy); } else { NearestOfUnit(goal, unit->X, unit->Y, &dx, &dy); } } else { dx = unit->Orders[0]->X; dy = unit->Orders[0]->Y; // FIXME: Can this be too near?? } // Fire to the tile center of the destination. dx = dx * TileSizeX + TileSizeX / 2; dy = dy * TileSizeY + TileSizeY / 2; missile = MakeMissile(unit->Type->Missile.Missile, x, y, dx, dy); // // Damage of missile // if (goal) { missile->TargetUnit = goal; goal->RefsIncrease(); } missile->SourceUnit = unit; unit->RefsIncrease(); }
/** ** Calculate parabolic trajectories. ** ** @param missile Missile pointer. ** ** @return true if target is reached, false otherwise ** ** @todo Find good values for ZprojToX and Y */ static bool ParabolicMissile(Missile &missile) { // Should be initialised by an other method (computed with distance...) const double k = -missile.Type->ParabolCoefficient; // Coefficient of the parabol. const double zprojToX = 4.0; // Projection of Z axis on axis X. const double zprojToY = 1024.0; // Projection of Z axis on axis Y. double z; // should be missile.Z later. MissileInitMove(missile); if (missile.TotalStep == 0) { return true; } Assert(missile.Type != NULL); const PixelPos orig_pos = missile.position; Assert(missile.TotalStep != 0); const PixelPos diff = (missile.destination - missile.source); const PixelPrecise sign(diff.x >= 0 ? 1 : -1, diff.y >= 0 ? 1 : -1); // Remember sign to move into correct direction PixelPrecise pos(missile.position.x, missile.position.y); // Remember old position missile.position = missile.source + diff * missile.CurrentStep / missile.TotalStep; Assert(k != 0); z = (double)missile.CurrentStep * (missile.TotalStep - missile.CurrentStep) / k; // Until Z is used for drawing, modify X and Y. missile.position.x += (int)(z * zprojToX / 64.0); missile.position.y += (int)(z * zprojToY / 64.0); missile.MissileNewHeadingFromXY(missile.position - orig_pos); for (; pos.x * sign.x <= missile.position.x * sign.x && pos.y * sign.y <= missile.position.y * sign.y; pos.x += (double)diff.x * missile.Type->SmokePrecision / missile.TotalStep, pos.y += (double)diff.y * missile.Type->SmokePrecision / missile.TotalStep) { if (missile.Type->Smoke.Missile && missile.CurrentStep) { const PixelPos position((int)pos.x + missile.Type->size.x / 2, (int)pos.y + missile.Type->size.y / 2); Missile *smoke = MakeMissile(*missile.Type->Smoke.Missile, position, position); if (smoke && smoke->Type->NumDirections > 1) { smoke->MissileNewHeadingFromXY(diff); } } if (missile.Type->SmokeParticle && missile.CurrentStep) { const PixelPos position((int)pos.x + missile.Type->size.x / 2, (int)pos.y + missile.Type->size.y / 2); missile.Type->SmokeParticle->pushPreamble(); missile.Type->SmokeParticle->pushInteger(position.x); missile.Type->SmokeParticle->pushInteger(position.y); missile.Type->SmokeParticle->run(); } if (missile.Type->Pierce) { const PixelPos position((int)pos.x, (int)pos.y); MissileHandlePierce(missile, Map.MapPixelPosToTilePos(position)); } } if (missile.CurrentStep == missile.TotalStep) { missile.position = missile.destination; return true; } return false; }
//FIXME: (Fabrice) I don't know if my update for missile visibility is fully //correct. global void MissileActions(void) { int missile; for( missile=0; missile<NumMissiles; ++missile ) { if( Missiles[missile].Type==MissileFree ) { continue; } if( Missiles[missile].Wait-- ) { continue; } if (MissileVisible(missile)) { // check before movement MustRedraw|=RedrawMap; } switch( Missiles[missile].Type->Class ) { case MissileClassPointToPoint: Missiles[missile].Wait=1; if( PointToPointMissile(missile) ) { MissileHit(missile); Missiles[missile].Type=MissileFree; } else { // // Animate missile, cycle through frames // Missiles[missile].Frame+=5; if( (Missiles[missile].Frame&127) >=Missiles[missile].Type->RleSprite->NumFrames ) { Missiles[missile].Frame= // (Missiles[missile].Frame&128)| (Missiles[missile].Frame -Missiles[missile].Type->RleSprite ->NumFrames); } DebugLevel3("Frame %d of %d\n" ,Missiles[missile].Frame ,Missiles[missile].Type->RleSprite->NumFrames); } break; case MissileClassPointToPointWithDelay: Missiles[missile].Wait=1; if( PointToPointMissile(missile) ) { switch( Missiles[missile].State++ ) { case 1: // FIXME: bounces up. PlayMissileSound(Missiles+missile, Missiles[missile].Type->ImpactSound.Sound); // FIXME: make this configurable!! switch( Missiles[missile].Type->Type ) { case MissileSmallCannon: MakeMissile(MissileCannonExplosion ,Missiles[missile].X ,Missiles[missile].Y ,0,0); break; case MissileBigCannon: MakeMissile(MissileCannonTowerExplosion ,Missiles[missile].X ,Missiles[missile].Y ,0,0); break; case MissileCatapultRock: case MissileBallistaBolt: MakeMissile(MissileImpact ,Missiles[missile].X ,Missiles[missile].Y ,0,0); break; } break; default: MissileHit(missile); Missiles[missile].Type=MissileFree; break; } } else { // // Animate missile, depends on the way. // // FIXME: how? } break; case MissileClassPointToPoint3Bounces: Missiles[missile].Wait=1; if( PointToPointMissile(missile) ) { // // 3 Bounces. // switch( Missiles[missile].State ) { case 1: case 3: case 5: Missiles[missile].State+=2; Missiles[missile].DX+= Missiles[missile].Xstep*TileSizeX*2; Missiles[missile].DY+= Missiles[missile].Ystep*TileSizeY*2; PlayMissileSound(Missiles+missile, Missiles[missile].Type->ImpactSound.Sound); MakeMissile(MissileExplosion ,Missiles[missile].X ,Missiles[missile].Y ,0,0); MissileHit(missile); // FIXME: hits to left and right // FIXME: reduce damage break; default: Missiles[missile].Type=MissileFree; break; } } else { // // Animate missile, cycle through frames // Missiles[missile].Frame+=5; if( (Missiles[missile].Frame&127) >=Missiles[missile].Type->RleSprite->NumFrames ) { Missiles[missile].Frame= // (Missiles[missile].Frame&128)| (Missiles[missile].Frame -Missiles[missile].Type->RleSprite ->NumFrames); } DebugLevel3("Frame %d of %d\n" ,Missiles[missile].Frame ,Missiles[missile].Type->RleSprite->NumFrames); } break; case MissileClassPointToPointWithHit: Missiles[missile].Wait=1; if( PointToPointMissile(missile) ) { // // Animate hit // Missiles[missile].Frame+=5; if( (Missiles[missile].Frame&127) >=Missiles[missile].Type->RleSprite->NumFrames ) { MissileHit(missile); Missiles[missile].Type=MissileFree; } } break; case MissileClassStayWithDelay: Missiles[missile].Wait=1; if( ++Missiles[missile].Frame ==Missiles[missile].Type->RleSprite ->NumFrames ) { MissileHit(missile); Missiles[missile].Type=MissileFree; } break; case MissileClassCycleOnce: Missiles[missile].Wait=Missiles[missile].Type->Speed; switch( Missiles[missile].State ) { case 0: case 2: ++Missiles[missile].State; break; case 1: if( ++Missiles[missile].Frame ==Missiles[missile].Type->RleSprite ->NumFrames ) { --Missiles[missile].Frame; ++Missiles[missile].State; } break; case 3: if( !Missiles[missile].Frame-- ) { MissileHit(missile); Missiles[missile].Type=MissileFree; } break; } break; } if (Missiles[missile].Type!=MissileFree && MissileVisible(missile)) { // check after movement MustRedraw|=RedrawMap; } } }
/** ** Fire missile. ** ** @param unit Unit that fires the missile. */ void FireMissile(CUnit &unit, CUnit *goal, const Vec2i &goalPos) { Vec2i newgoalPos = goalPos; // Goal dead? if (goal) { Assert(!unit.Type->Missile.Missile->AlwaysFire || unit.Type->Missile.Missile->Range); if (goal->Destroyed) { DebugPrint("destroyed unit\n"); return; } if (goal->Removed) { return; } if (goal->CurrentAction() == UnitActionDie) { if (unit.Type->Missile.Missile->AlwaysFire) { newgoalPos = goal->tilePos; goal = NULL; } else { return; } } } // No missile hits immediately! if (unit.Type->Missile.Missile->Class == MissileClassNone) { // No goal, take target coordinates if (!goal) { if (Map.WallOnMap(goalPos)) { if (Map.HumanWallOnMap(goalPos)) { Map.HitWall(goalPos, CalculateDamageStats(*unit.Stats, *UnitTypeHumanWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); } else { Map.HitWall(goalPos, CalculateDamageStats(*unit.Stats, *UnitTypeOrcWall->Stats, unit.Variable[BLOODLUST_INDEX].Value)); } return; } DebugPrint("Missile-none hits no unit, shouldn't happen!\n"); return; } HitUnit(&unit, *goal, CalculateDamage(unit, *goal)); return; } // If Firing from inside a Bunker CUnit *from = GetFirstContainer(unit); const int dir = ((unit.Direction + NextDirection / 2) & 0xFF) / NextDirection; const PixelPos startPixelPos = Map.TilePosToMapPixelPos_Center(from->tilePos) + unit.Type->MissileOffsets[dir][0]; Vec2i dpos; if (goal) { Assert(goal->Type); // Target invalid? // Moved out of attack range? if (unit.MapDistanceTo(*goal) < unit.Type->MinAttackRange) { DebugPrint("Missile target too near %d,%d\n" _C_ unit.MapDistanceTo(*goal) _C_ unit.Type->MinAttackRange); // FIXME: do something other? return; } // Fire to nearest point of the unit! // If Firing from inside a Bunker if (unit.Container) { NearestOfUnit(*goal, GetFirstContainer(unit)->tilePos, &dpos); } else { dpos = goal->tilePos + goal->Type->GetHalfTileSize(); } } else { dpos = newgoalPos; // FIXME: Can this be too near?? } PixelPos destPixelPos = Map.TilePosToMapPixelPos_Center(dpos); Missile *missile = MakeMissile(*unit.Type->Missile.Missile, startPixelPos, destPixelPos); // // Damage of missile // if (goal) { missile->TargetUnit = goal; } missile->SourceUnit = &unit; }
/** ** Work for missile hit. ** ** @param missile Missile reaching end-point. */ void MissileHit(Missile *missile) { CUnit *goal; int x; int y; CUnit *table[UnitMax]; int n; int i; int splash; if (missile->Type->ImpactSound.Sound) { PlayMissileSound(missile, missile->Type->ImpactSound.Sound); } x = missile->X + missile->Type->Width / 2; y = missile->Y + missile->Type->Height / 2; // // The impact generates a new missile. // if (missile->Type->ImpactMissile) { MakeMissile(missile->Type->ImpactMissile, x, y, x, y); } if (missile->Type->ImpactParticle) { missile->Type->ImpactParticle->pushPreamble(); missile->Type->ImpactParticle->pushInteger(x); missile->Type->ImpactParticle->pushInteger(y); missile->Type->ImpactParticle->run(); } if (!missile->SourceUnit) { // no owner - green-cross ... return; } x /= TileSizeX; y /= TileSizeY; if (x < 0 || y < 0 || x >= Map.Info.MapWidth || y >= Map.Info.MapHeight) { // FIXME: this should handled by caller? DebugPrint("Missile gone outside of map!\n"); return; // outside the map. } // // Choose correct goal. // if (!missile->Type->Range) { if (missile->TargetUnit) { // // Missiles without range only hits the goal always. // goal = missile->TargetUnit; if (goal->Destroyed) { // Destroyed goal->RefsDecrease(); missile->TargetUnit = NoUnitP; return; } MissileHitsGoal(missile, goal, 1); return; } return; } // // Hits all units in range. // i = missile->Type->Range; n = UnitCache.Select(x - i + 1, y - i + 1, x + i, y + i, table, UnitMax); Assert(missile->SourceUnit != NULL); for (i = 0; i < n; ++i) { goal = table[i]; // // Can the unit attack the this unit-type? // NOTE: perhaps this should be come a property of the missile. // if (CanTarget(missile->SourceUnit->Type, goal->Type)) { splash = MapDistanceToUnit(x, y, goal); if (splash) { splash *= missile->Type->SplashFactor; } else { splash = 1; } MissileHitsGoal(missile, goal, splash); } } }
/** ** Work for missile hit. */ void Missile::MissileHit(CUnit *unit) { const MissileType &mtype = *this->Type; if (mtype.ImpactSound.Sound) { PlayMissileSound(*this, mtype.ImpactSound.Sound); } const PixelPos pixelPos = this->position + this->Type->size / 2; // // The impact generates a new missile. // if (mtype.Impact.empty() == false) { for (std::vector<MissileConfig *>::const_iterator it = mtype.Impact.begin(); it != mtype.Impact.end(); ++it) { const MissileConfig &mc = **it; Missile *impact = MakeMissile(*mc.Missile, pixelPos, pixelPos); if (impact && impact->Type->Damage) { impact->SourceUnit = this->SourceUnit; } } } if (mtype.ImpactParticle) { mtype.ImpactParticle->pushPreamble(); mtype.ImpactParticle->pushInteger(pixelPos.x); mtype.ImpactParticle->pushInteger(pixelPos.y); mtype.ImpactParticle->run(); } if (!this->SourceUnit) { // no owner - green-cross ... return; } const Vec2i pos = Map.MapPixelPosToTilePos(pixelPos); if (!Map.Info.IsPointOnMap(pos)) { // FIXME: this should handled by caller? DebugPrint("Missile gone outside of map!\n"); return; // outside the map. } // // Choose correct goal. // if (unit) { if (unit->Destroyed) { return; } if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, *unit)) { return; } else { PiercedUnits.insert(this->PiercedUnits.begin(), unit); } } MissileHitsGoal(*this, *unit, 1); return; } if (!mtype.Range) { if (this->TargetUnit && (mtype.FriendlyFire == false || this->TargetUnit->Player->Index != this->SourceUnit->Player->Index)) { // // Missiles without range only hits the goal always. // CUnit &goal = *this->TargetUnit; if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, goal)) { return; } else { PiercedUnits.insert(this->PiercedUnits.begin(), &goal); } } if (goal.Destroyed) { this->TargetUnit = NULL; return; } MissileHitsGoal(*this, goal, 1); return; } MissileHitsWall(*this, pos, 1); return; } { // // Hits all units in range. // const Vec2i range(mtype.Range - 1, mtype.Range - 1); std::vector<CUnit *> table; Select(pos - range, pos + range, table); Assert(this->SourceUnit != NULL); for (size_t i = 0; i != table.size(); ++i) { CUnit &goal = *table[i]; // // Can the unit attack this unit-type? // NOTE: perhaps this should be come a property of the missile. // Also check CorrectSphashDamage so land explosions can't hit the air units // if (CanTarget(*this->SourceUnit->Type, *goal.Type) && (mtype.FriendlyFire == false || goal.Player->Index != this->SourceUnit->Player->Index)) { bool shouldHit = true; if (mtype.Pierce && mtype.PierceOnce) { if (IsPiercedUnit(*this, goal)) { shouldHit = false; } else { PiercedUnits.insert(this->PiercedUnits.begin(), &goal); } } if (mtype.CorrectSphashDamage == true) { bool isPositionSpell = false; if (this->TargetUnit == NULL && this->SourceUnit->CurrentAction() == UnitActionSpellCast) { const COrder_SpellCast &order = *static_cast<COrder_SpellCast *>(this->SourceUnit->CurrentOrder()); if (order.GetSpell().Target == TargetPosition) { isPositionSpell = true; } } if (isPositionSpell || this->SourceUnit->CurrentAction() == UnitActionAttackGround) { if (goal.Type->UnitType != this->SourceUnit->Type->UnitType) { shouldHit = false; } } else { if (this->TargetUnit == NULL || goal.Type->UnitType != this->TargetUnit->Type->UnitType) { shouldHit = false; } } } if (shouldHit) { int splash = goal.MapDistanceTo(pos); if (splash) { splash *= mtype.SplashFactor; } else { splash = 1; } MissileHitsGoal(*this, goal, splash); } } } } // Missile hits ground. const Vec2i offset(mtype.Range, mtype.Range); const Vec2i posmin = pos - offset; for (int i = mtype.Range * 2; --i;) { for (int j = mtype.Range * 2; --j;) { const Vec2i posIt(posmin.x + i, posmin.y + j); if (Map.Info.IsPointOnMap(posIt)) { int d = Distance(pos, posIt); d *= mtype.SplashFactor; if (d == 0) { d = 1; } MissileHitsWall(*this, posIt, d); } } } }
/* virtual */ void CAnimation_SpawnMissile::Action(CUnit &unit, int &/*move*/, int /*scale*/) const { Assert(unit.Anim.Anim == this); const int startx = ParseAnimInt(&unit, this->startXStr.c_str()); const int starty = ParseAnimInt(&unit, this->startYStr.c_str()); const int destx = ParseAnimInt(&unit, this->destXStr.c_str()); const int desty = ParseAnimInt(&unit, this->destYStr.c_str()); const int flags = ParseAnimFlags(unit, this->flagsStr.c_str()); const int offsetnum = ParseAnimInt(&unit, this->offsetNumStr.c_str()); const CUnit *goal = flags & ANIM_SM_RELTARGET ? unit.CurrentOrder()->GetGoal() : &unit; const int dir = ((goal->Direction + NextDirection / 2) & 0xFF) / NextDirection; const PixelPos moff = goal->Type->MissileOffsets[dir][!offsetnum ? 0 : offsetnum - 1]; PixelPos start; PixelPos dest; MissileType *mtype = MissileTypeByIdent(this->missileTypeStr); if (mtype == NULL) { return; } if (!goal || goal->Destroyed) { return; } if ((flags & ANIM_SM_PIXEL)) { start.x = goal->tilePos.x * PixelTileSize.x + goal->IX + moff.x + startx; start.y = goal->tilePos.y * PixelTileSize.y + goal->IY + moff.y + starty; } else { start.x = (goal->tilePos.x + startx) * PixelTileSize.x + PixelTileSize.x / 2 + moff.x; start.y = (goal->tilePos.y + starty) * PixelTileSize.y + PixelTileSize.y / 2 + moff.y; } if ((flags & ANIM_SM_TOTARGET)) { CUnit *target = goal->CurrentOrder()->GetGoal(); if (!target || target->Destroyed) { Assert(!mtype->AlwaysFire || mtype->Range); if (!target && mtype->AlwaysFire == false) { return; } } if (!target) { if (goal->CurrentAction() == UnitActionAttack || goal->CurrentAction() == UnitActionAttackGround) { COrder_Attack &order = *static_cast<COrder_Attack *>(goal->CurrentOrder()); dest = Map.TilePosToMapPixelPos_Center(order.GetGoalPos()); } else if (goal->CurrentAction() == UnitActionSpellCast) { COrder_SpellCast &order = *static_cast<COrder_SpellCast *>(goal->CurrentOrder()); dest = Map.TilePosToMapPixelPos_Center(order.GetGoalPos()); } if (flags & ANIM_SM_PIXEL) { dest.x += destx; dest.y += desty; } else { dest.x += destx * PixelTileSize.x; dest.y += desty * PixelTileSize.y; } } else if (flags & ANIM_SM_PIXEL) { dest.x = target->GetMapPixelPosCenter().x + destx; dest.y = target->GetMapPixelPosCenter().y + desty; } else { dest.x = (target->tilePos.x + destx) * PixelTileSize.x; dest.y = (target->tilePos.y + desty) * PixelTileSize.y; dest += target->Type->GetPixelSize() / 2; } } else { if ((flags & ANIM_SM_PIXEL)) { dest.x = goal->GetMapPixelPosCenter().x + destx; dest.y = goal->GetMapPixelPosCenter().y + desty; } else { dest.x = (goal->tilePos.x + destx) * PixelTileSize.x; dest.y = (goal->tilePos.y + desty) * PixelTileSize.y; dest += goal->Type->GetPixelSize() / 2; } } Vec2i destTilePos = Map.MapPixelPosToTilePos(dest); const int dist = goal->MapDistanceTo(destTilePos); if ((flags & ANIM_SM_RANGED) && !(flags & ANIM_SM_PIXEL) && dist > goal->Stats->Variables[ATTACKRANGE_INDEX].Max && dist < goal->Type->MinAttackRange) { } else { Missile *missile = MakeMissile(*mtype, start, dest); if (flags & ANIM_SM_SETDIRECTION) { PixelPos posd; posd.x = Heading2X[goal->Direction / NextDirection]; posd.y = Heading2Y[goal->Direction / NextDirection]; missile->MissileNewHeadingFromXY(posd); } if (flags & ANIM_SM_DAMAGE) { missile->SourceUnit = &unit; } if (flags & ANIM_SM_TOTARGET) { missile->TargetUnit = goal->CurrentOrder()->GetGoal(); } } }