/** ** Find the next idle worker, select it, and center on it */ void UiFindIdleWorker() { if (ThisPlayer->FreeWorkers.empty()) { return; } CUnit *unit = ThisPlayer->FreeWorkers[0]; if (LastIdleWorker) { const std::vector<CUnit *> &freeWorkers = ThisPlayer->FreeWorkers; std::vector<CUnit *>::const_iterator it = std::find(freeWorkers.begin(), freeWorkers.end(), LastIdleWorker); if (it != ThisPlayer->FreeWorkers.end()) { if (*it != ThisPlayer->FreeWorkers.back()) { unit = *(++it); } } } if (unit != NULL) { LastIdleWorker = unit; SelectSingleUnit(*unit); UI.StatusLine.Clear(); UI.StatusLine.ClearCosts(); CurrentButtonLevel = 0; PlayUnitSound(*Selected[0], VoiceSelected); SelectionChanged(); UI.SelectedViewport->Center(unit->GetMapPixelPosCenter()); } }
/* virtual */ void COrder_Still::Execute(CUnit &unit) { // If unit is not bunkered and removed, wait if (unit.Removed //Wyrmgus start // && (unit.Container == nullptr || unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value == false)) { && (unit.Container == nullptr || !unit.Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value || !unit.Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value)) { // make both the unit and the transporter have the tag be necessary for the attack to be possible if (unit.Container != nullptr) { LeaveShelter(unit); // leave shelter if surrounded } //Wyrmgus end return ; } this->Finished = false; switch (this->State) { case SUB_STILL_STANDBY: //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); if (unit.Variable[STUN_INDEX].Value == 0) { //only show the idle animation when still if the unit is not stunned UnitShowAnimation(unit, unit.GetAnimations()->Still); } if (SyncRand(100000) == 0) { PlayUnitSound(unit, VoiceIdle); } unit.StepCount = 0; //Wyrmgus end break; case SUB_STILL_ATTACK: // attacking unit in attack range. AnimateActionAttack(unit, *this); break; } if (unit.Anim.Unbreakable) { // animation can't be aborted here return; } //Wyrmgus start if (unit.Variable[STUN_INDEX].Value > 0) { //if unit is stunned, remain still return; } //Wyrmgus end this->State = SUB_STILL_STANDBY; this->Finished = (this->Action == UnitActionStill); if (this->Action == UnitActionStandGround || unit.Removed || unit.CanMove() == false) { if (unit.AutoCastSpell) { this->AutoCastStand(unit); } if (unit.IsAgressive()) { this->AutoAttackStand(unit); } } else { if (AutoCast(unit) || (unit.IsAgressive() && AutoAttack(unit)) || AutoRepair(unit) //Wyrmgus start // || MoveRandomly(unit)) { || MoveRandomly(unit) || PickUpItem(unit)) { //Wyrmgus end } } }
/* virtual */ void CAnimation_RandomSound::Action(CUnit &unit, int &/*move*/, int /*scale*/) const { Assert(unit.Anim.Anim == this); if (unit.IsVisible(*ThisPlayer) || ReplayRevealMap) { const size_t index = SyncRand() % this->sounds.size(); PlayUnitSound(unit, this->sounds[index].Sound); } }
/** ** Find the next idle worker, select it, and center on it */ static void UiFindIdleWorker() { if (ThisPlayer->FreeWorkers.empty()) { return; } CUnit *unit = ThisPlayer->FreeWorkers[0]; if (unit != NULL) { SelectSingleUnit(*unit); UI.StatusLine.Clear(); ClearCosts(); CurrentButtonLevel = 0; PlayUnitSound(*Selected[0], VoiceSelected); SelectionChanged(); UI.SelectedViewport->Center(unit->GetMapPixelPosCenter()); } }
/** ** Unit trains unit! ** ** @param unit Unit that trains. */ global void HandleActionTrain(Unit* unit) { Unit* nunit; UnitType* type; Player* player; #if 0 // JOHNS: should be checked by the user-interface if( &Players[unit->Player]==ThisPlayer ) { // FIXME: If so used you get millions of messages. if( ThisPlayer->Food<=ThisPlayer->Units && unit->Command.Data.Train.Ticks ) { SetMessage( "You need more farms!" ); } else { AiNeedMoreFarms(unit); } } #endif player=unit->Player; unit->Command.Data.Train.Ticks+=SpeedTrain; // FIXME: Should count down if( unit->Command.Data.Train.Ticks >=unit->Command.Data.Train.What[0] ->Stats[player->Player].Costs[TimeCost] ) { // // Check if enough food available. // if( player->Food<=player->NumUnits ) { // FIXME: GameMessage if( player==ThisPlayer ) { // FIXME: PlayVoice :), see task.txt SetMessage("You need more farms!"); } else { // FIXME: Callback for AI! // AiNeedMoreFarms(unit); } unit->Command.Data.Train.Ticks-=SpeedTrain; unit->Reset=1; unit->Wait=FRAMES_PER_SECOND/6; return; } nunit=MakeUnit(&UnitTypes[unit->Command.Data.Train.What[0]->Type] ,player); nunit->X=unit->X; nunit->Y=unit->Y; type=unit->Type; DropOutOnSide(nunit,HeadingW,type->TileWidth,type->TileHeight); // FIXME: GameMessage if( player==ThisPlayer ) { SetMessage("Training complete"); PlayUnitSound(nunit,VoiceReady); } else { AiTrainingComplete(unit,nunit); } unit->Reset=1; unit->Wait=1; if ( --unit->Command.Data.Train.Count ) { int z; for( z = 0; z < MAX_UNIT_TRAIN-1; z++ ) { unit->Command.Data.Train.What[z] = unit->Command.Data.Train.What[z+1]; } unit->Command.Data.Train.Ticks=0; } else { unit->Command.Action=UnitActionStill; } nunit->Command=unit->PendCommand; if( IsSelected(unit) ) { UpdateBottomPanel(); MustRedraw|=RedrawPanels; } return; } if( IsSelected(unit) ) { MustRedraw|=RedrawTopPanel; } unit->Reset=1; unit->Wait=FRAMES_PER_SECOND/6; }
/** ** Unit moves! Generic function called from other actions. ** ** @param unit Pointer to unit. ** ** @return >0 remaining path length, 0 wait for path, -1 ** reached goal, -2 can't reach the goal. */ int DoActionMove(CUnit &unit) { Vec2i posd; // movement in tile. int d; Assert(unit.CanMove()); if (!unit.Moving && (unit.Type->Animations->Move != unit.Anim.CurrAnim || !unit.Anim.Wait)) { Assert(!unit.Anim.Unbreakable); // FIXME: So units flying up and down are not affected. unit.IX = 0; unit.IY = 0; UnmarkUnitFieldFlags(unit); d = NextPathElement(unit, &posd.x, &posd.y); MarkUnitFieldFlags(unit); switch (d) { case PF_UNREACHABLE: // Can't reach, stop if (unit.Player->AiEnabled) { AiCanNotMove(unit); } unit.Moving = 0; return d; case PF_REACHED: // Reached goal, stop unit.Moving = 0; return d; case PF_WAIT: // No path, wait // Reset frame to still frame while we wait // FIXME: Unit doesn't animate. unit.Frame = unit.Type->StillFrame; UnitUpdateHeading(unit); unit.Wait = 10; unit.Moving = 0; return d; default: // On the way moving unit.Moving = 1; break; } if (unit.Type->UnitType == UnitTypeNaval) { // Boat (un)docking? const CMapField &mf_cur = *Map.Field(unit.Offset); const CMapField &mf_next = *Map.Field(unit.tilePos + posd); if (mf_cur.WaterOnMap() && mf_next.CoastOnMap()) { PlayUnitSound(unit, VoiceDocking); } else if (mf_cur.CoastOnMap() && mf_next.WaterOnMap()) { PlayUnitSound(unit, VoiceDocking); // undocking } } Vec2i pos = unit.tilePos + posd; unit.MoveToXY(pos); // Remove unit from the current selection if (unit.Selected && !Map.Field(pos)->playerInfo.IsTeamVisible(*ThisPlayer)) { if (NumSelected == 1) { // Remove building cursor CancelBuildingMode(); } if (!ReplayRevealMap) { UnSelectUnit(unit); SelectionChanged(); } } unit.IX = -posd.x * PixelTileSize.x; unit.IY = -posd.y * PixelTileSize.y; unit.Frame = unit.Type->StillFrame; UnitHeadingFromDeltaXY(unit, posd); } else { posd.x = Heading2X[unit.Direction / NextDirection]; posd.y = Heading2Y[unit.Direction / NextDirection]; d = unit.pathFinderData->output.Length + 1; } unit.pathFinderData->output.Cycles++;//reset have to be manualy controled by caller. int move = UnitShowAnimationScaled(unit, unit.Type->Animations->Move, Map.Field(unit.Offset)->Cost); unit.IX += posd.x * move; unit.IY += posd.y * move; // Finished move animation, set Moving to 0 so we recalculate the path // next frame // FIXME: this is broken for subtile movement if (!unit.Anim.Unbreakable && !unit.IX && !unit.IY) { unit.Moving = 0; } return d; }
/** ** Called when right button is pressed ** ** @param x X map tile position. ** @param y Y map tile position. */ global void DoRightButton(int x,int y) { int i; Unit* dest; Unit* unit; UnitType* type; int action; int acknowledged; // // No unit selected // if( !NumSelected ) { return; } // // Unit selected isn't owned by the player. // You can't select your own units + foreign unit(s). // if( Selected[0]->Player!=ThisPlayer ) { return; } acknowledged=0; for( i=0; i<NumSelected; ++i ) { unit=Selected[i]; DebugCheck( !unit ); type=unit->Type; if( !acknowledged ) { PlayUnitSound(unit,VoiceAcknowledging); acknowledged=1; } action=type->MouseAction; DebugLevel3(__FUNCTION__": Mouse action %d\n",action); // // Enter transporters? // dest=UnitOnMapTile(x,y); if( dest && dest->Type->Transporter && dest->Player==ThisPlayer && unit->Type->UnitType==UnitTypeLand ) { dest->Blink=3; DebugLevel3(__FUNCTION__": Board transporter\n"); SendCommandBoard(unit,dest); continue; } // // Peon/Peasant // if( action==MouseActionHarvest ) { DebugLevel3("Action %x\n",TheMap.ActionMap[x+y*TheMap.Width]); if( type->Type==UnitPeonWithWood || type->Type==UnitPeasantWithWood || type->Type==UnitPeonWithGold || type->Type==UnitPeasantWithGold ) { dest=UnitOnMapTile(x,y); if( dest ) { dest->Blink=3; if( dest->Type->StoresGold && (type->Type==UnitPeonWithGold || type->Type==UnitPeasantWithGold) ) { DebugLevel3("GOLD-DEPOSIT\n"); // FIXME: return to this depot?? SendCommandReturnGoods(unit); continue; } if( (dest->Type->StoresWood || dest->Type->StoresGold) && (type->Type==UnitPeonWithWood || type->Type==UnitPeasantWithWood) ) { DebugLevel3("WOOD-DEPOSIT\n"); // FIXME: return to this depot?? SendCommandReturnGoods(unit); continue; } } } else { if( ForestOnMap(x,y) ) { SendCommandHarvest(unit,x,y); continue; } if( (dest=GoldMineOnMap(x,y)) ) { dest->Blink=3; DebugLevel3("GOLD-MINE\n"); SendCommandMineGold(unit,dest); continue; } } // FIXME: repair/attack/follow/board dest=TargetOnMapTile(unit,x,y); if( dest ) { dest->Blink=3; if( dest->Player==ThisPlayer ) { // FIXME: SendCommandFollow(unit,x,y,dest); // FIXME: continue; } else { // FIXME: can I attack this unit? SendCommandAttack(unit,x,y,dest); continue; } } // cade: this is default repair action dest=UnitOnMapTile(x,y); if( dest && dest->Type && dest->Player==ThisPlayer && dest->HP<dest->Stats[dest->Player->Player].HitPoints && dest->Type->Building ) { SendCommandRepair(unit,x,y); } else { SendCommandMoveUnit(unit,x,y); } continue; } // // Tanker // if( action==MouseActionHaulOil ) { if( type->Type==UnitTankerOrcFull || type->Type==UnitTankerHumanFull ) { DebugLevel2("Should return to oil deposit\n"); } else { if( (dest=PlatformOnMap(x,y)) ) { dest->Blink=3; DebugLevel2("PLATFORM\n"); SendCommandHaulOil(unit,dest); continue; } } SendCommandMoveUnit(unit,x,y); continue; } // // Fighters // if( action==MouseActionAttack ) { // FIXME: more units on same tile dest=TargetOnMapTile(unit,x,y); if( dest ) { dest->Blink=3; if( dest->Player==ThisPlayer ) { // FIXME: SendCommandFollow(unit,x,y,dest); // FIXME: continue; } else { // FIXME: can I attack this unit? SendCommandAttack(unit,x,y,dest); continue; } } if( WallOnMap(x,y) ) { DebugLevel3("WALL ON TILE\n"); if( ThisPlayer->Race==PlayerRaceHuman && OrcWallOnMap(x,y) ) { DebugLevel3("HUMAN ATTACKS ORC\n"); SendCommandAttack(unit,x,y,NoUnitP); } if( ThisPlayer->Race==PlayerRaceOrc && HumanWallOnMap(x,y) ) { DebugLevel3("ORC ATTACKS HUMAN\n"); SendCommandAttack(unit,x,y,NoUnitP); } } SendCommandMoveUnit(unit,x,y); continue; } // FIXME: demolish!!!!!!! // FIXME: attack/follow/board ... if( action==MouseActionMove ) { } // if( !unit->Type->Building ) { SendCommandMoveUnit(unit,x,y); // } } }
/* ** Chop the wood. ** Return TRUE if ready, otherwise FALSE. */ local int ChopWood(Unit* unit) { Unit* destu; int flags; extern Animation PeonAttack[]; flags=UnitShowAnimation(unit,PeonAttack); if( (flags&AnimationSound) ) { PlayUnitSound(unit,VoiceTreeChopping); } if( unit->Reset ) { DebugCheck( unit->Wait!=1 ); // // This a work around the bug: "lumber bug" // We give a worker a new command and in the next cycle // the worker is ready chopping. // #if 0 // FIXME: johns+cade: this didn't work with the current code if( unit->NextCommand[0].Action==UnitActionHarvest || unit->NextCommand[0].Action==UnitActionMineGold ) { unit->SubAction=0; return 0; } #endif // // Wood gone while chopping? // if( !ForestOnMap(unit->Command.Data.Move.DX ,unit->Command.Data.Move.DY) ) { if( FindWoodInSight(unit ,&unit->Command.Data.Move.DX ,&unit->Command.Data.Move.DY) ) { unit->Command.Data.Move.Fast=1; unit->Command.Data.Move.Goal=NoUnitP; unit->Command.Data.Move.Range=0; // FIXME: shouldn't it be range=1 ?? DebugCheck( unit->Command.Action!=UnitActionHarvest ); unit->SubAction=0; } else { unit->Command.Action=UnitActionStill; unit->SubAction=0; DebugLevel3("NO-WOOD in sight range\n"); } return 0; } // // Ready chopping wood? // if( !(unit->WoodToHarvest = --unit->Value) ) { // Have wood if( unit->Type->Type==UnitPeon ) { unit->Type=&UnitTypes[UnitPeonWithWood]; } else if( unit->Type->Type==UnitPeasant ) { unit->Type=&UnitTypes[UnitPeasantWithWood]; } else { DebugLevel0("Wrong unit for chopping wood %d\n" ,unit->Type->Type); } // // Update the display. // if( UnitVisible(unit) ) { MustRedraw|=RedrawMap; } if( IsSelected(unit) ) { UpdateBottomPanel(); MustRedraw|=RedrawBottomPanel; } // // Update the map. // MapRemoveWood(unit->Command.Data.Move.DX ,unit->Command.Data.Move.DY); // // Find place to return wood. // unit->Command.Data.Move.SX=unit->X; unit->Command.Data.Move.SY=unit->Y; if( !(destu=FindWoodDeposit(unit->Player,unit->X,unit->Y)) ) { unit->Command.Action=UnitActionStill; unit->SubAction=0; } else { unit->Command.Data.Move.Fast=1; unit->Command.Data.Move.Range=1; unit->Command.Data.Move.Goal=destu; #if 1 // Fast movement need this?? NearestOfUnit(destu,unit->X,unit->Y ,&unit->Command.Data.Move.DX ,&unit->Command.Data.Move.DY); #else unit->Command.Data.Move.DX=destu->X; unit->Command.Data.Move.DY=destu->Y; #endif DebugLevel3("Return to %Zd=%d,%d\n" ,destu-UnitsPool ,unit->Command.Data.Move.DX ,unit->Command.Data.Move.DY); DebugCheck( unit->Command.Action!=UnitActionHarvest ); return 1; } } } return 0; }
/* virtual */ void COrder_Train::Execute(CUnit &unit) { AnimateActionTrain(unit); if (unit.Wait) { unit.Wait--; return ; } CPlayer &player = *unit.Player; const CUnitType &nType = *this->Type; const int cost = nType.Stats[player.Index].Costs[TimeCost]; this->Ticks += std::max(1, player.SpeedTrain / SPEEDUP_FACTOR); if (this->Ticks < cost) { unit.Wait = CYCLES_PER_SECOND / 6; return ; } this->Ticks = std::min(this->Ticks, cost); // Check if enough supply available. const int food = player.CheckLimits(nType); if (food < 0) { if (food == -3 && unit.Player->AiEnabled) { AiNeedMoreSupply(*unit.Player); } unit.Wait = CYCLES_PER_SECOND / 6; return ; } CUnit *newUnit = MakeUnit(nType, &player); if (newUnit == NULL) { // No more memory :/ player.Notify(NotifyYellow, unit.tilePos, _("Unable to train %s"), nType.Name.c_str()); unit.Wait = CYCLES_PER_SECOND / 6; return ; } // New unit might supply food UpdateForNewUnit(*newUnit, 0); // Set life span if (unit.Type->DecayRate) { newUnit->TTL = GameCycle + unit.Type->DecayRate * 6 * CYCLES_PER_SECOND; } /* Auto Group Add */ if (!unit.Player->AiEnabled && unit.GroupId) { int num = 0; while (!(unit.GroupId & (1 << num))) { ++num; } AddToGroup(&newUnit, 1, num); } DropOutOnSide(*newUnit, LookingW, &unit); player.Notify(NotifyGreen, newUnit->tilePos, _("New %s ready"), nType.Name.c_str()); if (&player == ThisPlayer) { PlayUnitSound(*newUnit, VoiceReady); } if (unit.Player->AiEnabled) { AiTrainingComplete(unit, *newUnit); } if (unit.NewOrder && unit.NewOrder->HasGoal() && unit.NewOrder->GetGoal()->Destroyed) { delete unit.NewOrder; unit.NewOrder = NULL; } if (CanHandleOrder(*newUnit, unit.NewOrder) == true) { delete newUnit->Orders[0]; newUnit->Orders[0] = unit.NewOrder->Clone(); } else { #if 0 // Tell the unit to rigth-click ? #endif } this->Finished = true; if (IsOnlySelected(unit)) { UI.ButtonPanel.Update(); } }
static void Finish(COrder_Built &order, CUnit &unit) { const CUnitType &type = *unit.Type; CPlayer &player = *unit.Player; DebugPrint("%d: Building %s(%s) ready.\n" _C_ player.Index _C_ type.Ident.c_str() _C_ type.Name.c_str()); // HACK: the building is ready now player.UnitTypesCount[type.Slot]++; if (unit.Active) { player.UnitTypesAiActiveCount[type.Slot]++; } unit.Constructed = 0; if (unit.Frame < 0) { unit.Frame = -1; } else { unit.Frame = 0; } CUnit *worker = order.GetWorkerPtr(); if (worker != NULL) { if (type.BoolFlag[BUILDERLOST_INDEX].value) { // Bye bye worker. LetUnitDie(*worker); worker = NULL; } else { // Drop out the worker. worker->ClearAction(); DropOutOnSide(*worker, LookingW, &unit); // If we can harvest from the new building, do it. if (worker->Type->ResInfo[type.GivesResource]) { CommandResource(*worker, unit, 0); } // If we can reurn goods to a new depot, do it. if (worker->CurrentResource && worker->ResourcesHeld > 0 && type.CanStore[worker->CurrentResource]) { CommandReturnGoods(*worker, &unit, 0); } } } if (type.GivesResource && type.StartingResources != 0) { // Has StartingResources, Use those unit.ResourcesHeld = type.StartingResources; } player.Notify(NotifyGreen, unit.tilePos, _("New %s done"), type.Name.c_str()); if (&player == ThisPlayer) { if (type.MapSound.Ready.Sound) { PlayUnitSound(unit, VoiceReady); } else if (worker) { PlayUnitSound(*worker, VoiceWorkCompleted); } else { PlayUnitSound(unit, VoiceBuilding); } } if (player.AiEnabled) { /* Worker can be NULL */ AiWorkComplete(worker, unit); } // FIXME: Vladi: this is just a hack to test wall fixing, // FIXME: also not sure if the right place... // FIXME: Johns: hardcoded unit-type wall / more races! if (&type == UnitTypeOrcWall || &type == UnitTypeHumanWall) { Map.SetWall(unit.tilePos, &type == UnitTypeHumanWall); unit.Remove(NULL); UnitLost(unit); UnitClearOrders(unit); unit.Release(); return ; } UpdateForNewUnit(unit, 0); // Set the direction of the building if it supports them if (type.NumDirections > 1 && type.BoolFlag[NORANDOMPLACING_INDEX].value == false) { if (type.BoolFlag[WALL_INDEX].value) { // Special logic for walls CorrectWallDirections(unit); CorrectWallNeighBours(unit); } else { unit.Direction = (MyRand() >> 8) & 0xFF; // random heading } UnitUpdateHeading(unit); } if (IsOnlySelected(unit) || &player == ThisPlayer) { SelectedUnitChanged(); } MapUnmarkUnitSight(unit); unit.CurrentSightRange = unit.Stats->Variables[SIGHTRANGE_INDEX].Max; MapMarkUnitSight(unit); order.Finished = true; }
/** ** 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; }