diff --git a/doc/scripts/ai.html b/doc/scripts/ai.html index cc08b2d01..549eebf5c 100644 --- a/doc/scripts/ai.html +++ b/doc/scripts/ai.html @@ -54,7 +54,6 @@ <a href="#define-ai-player">define-ai-player</a> <a href="pud.html#define-ai-wc-names">define-ai-wc-names</a> <a href="#ai:adhoc-force">ai:adhoc-force</a> -<a href="#ai:attack-with-force">ai:attack-with-force</a> <a href="#ai:check-force">ai:check-force</a> <a href="#ai:clear-force">ai:clear-force</a> <a href="#ai:compute-gauges">ai:compute-gauges</a> @@ -113,9 +112,8 @@ There are two kinds of scripts : Scripts can arrange and control units using forces : <br> A script can ask for some type of unit in a force (using <a href="#ai:force">ai:force</a>), and then wait for them to be ready (using <a href="#ai:wait-force">ai:wait-force</a>).<br> -Then it can send them to attack ( <a href="#ai:attack-with-force">ai:attack-with-force</a>, -<a href="#ai:hotspot-attack-with-force">ai:hotspot-attack-with-force</a> ) or back home -( <a href="#ai:force-go-home">ai:force-go-home</a> ). +Then it can send them to attack ( <a href="#ai:hotspot-attack-with-force">ai:hotspot-attack-with-force</a> ) +or back home ( <a href="#ai:force-go-home">ai:force-go-home</a> ). <br> Each action/reaction script has a specific force which keeps all its units. ( see <a href="#ai:own-force">ai:own-force</a> )<br> <br> @@ -436,45 +434,6 @@ its representation in the resulting force. (writes nil "Can't defend with grunts and trolls!")) </pre> -<h4>Used</h4> - -<a href="../../data/ccl/ai.ccl"> $LIBARYPATH/ccl/ai.ccl </a> - -<a name="ai:attack-with-force"></a> -<h3>(ai:attack-with-force force)</h3> - -Attack the opponent with a force. The place is choosen by the AI. If there -are flyers, ships and land units in the force they could attack different -goals. - - -<dl> -<dt>force</dt> -<dd>Number of the force to which the units should belong. 0 - 9 is currently -supported.22 -</dd> -</dl> - -<i>The force isn't moved as unit, faster units attacks first, than later the -slower units will attack.</i> - -<h4>Example</h4> - -<pre> - ;; Force 0 is built with one footman. The script continues processing, if the - ;; footman is ready trained. Now attack the opponent with force 0. - (ai:force 0 'unit-footman 1) - (ai:wait-force 0) - (ai:attack-with-force 0) -</pre> - -<h4>See also</h4> - -<a href="#ai:hotspot-attack-with-force">ai:hotspot-attack-with-force</a> - -<a href="#ai:force-go-home">ai:force-go-home</a> - - <h4>Used</h4> <a href="../../data/ccl/ai.ccl"> $LIBARYPATH/ccl/ai.ccl </a> diff --git a/src/ai/ai_force.cpp b/src/ai/ai_force.cpp index cb4e02302..5a62c1293 100644 --- a/src/ai/ai_force.cpp +++ b/src/ai/ai_force.cpp @@ -838,62 +838,6 @@ global void AiAttackWithForceAt(int force, int x, int y) } } -/** -** Attack opponent with force. -** -** @param force Force number to attack with. -*/ -global void AiAttackWithForce(int force) -{ - const AiUnit *aiunit; - const Unit *enemy; - int x; - int y; - - AiCleanForce(force); - - AiPlayer->Force[force].Attacking = 0; - if ((aiunit = AiPlayer->Force[force].Units)) { - AiPlayer->Force[force].Attacking = 1; - DebugLevel3Fn("FORCE %d started ( AiAttackWithForce )\n" _C_ force); - - enemy = NoUnitP; - while (aiunit && !enemy) { // Use an unit that can attack - if (aiunit->Unit->Type->CanAttack) { - enemy = AttackUnitsInDistance(aiunit->Unit, MaxMapWidth); - } - aiunit = aiunit->Next; - } - - if (!enemy) { - DebugLevel0Fn("Need to plan an attack with transporter\n"); - if (!AiPlayer->Force[force].State && !AiPlanAttack(&AiPlayer->Force[force])) { - DebugLevel0Fn("Can't transport, look for walls\n"); - if (!AiFindWall(&AiPlayer->Force[force])) { - AiPlayer->Force[force].Attacking = 0; - } - } - return; - } - AiPlayer->Force[force].State = 0; - x = enemy->X; - y = enemy->Y; - - // - // Send all units in the force to enemy. - // - aiunit = AiPlayer->Force[force].Units; - while (aiunit) { - if (aiunit->Unit->Type->CanAttack) { - CommandAttack(aiunit->Unit, x, y, NULL, FlushCommands); - } else { - CommandMove(aiunit->Unit, x, y, FlushCommands); - } - aiunit = aiunit->Next; - } - } -} - /** ** Try to group units in a force. Units are grouped arround the closest units of the hotspot. ** @@ -995,189 +939,6 @@ global void AiSendForceHome(int force) } } -//---------------------------------------------------------------------------- -// Handle attack of force with transporter. -//---------------------------------------------------------------------------- - -/** -** Step 1) -** Load force on transporters. -** -** @param force Force pointer. -** -** @todo If an unit can't reach the transporter the code hangs. -** We must the transporter land on a new position. -** Or the board action will be better written. -*/ -local void AiLoadForce(AiForce * force) -{ - AiUnit *aiunit; - Unit *table[UnitMax]; - int n; - int i; - int o; - int f; - - // - // Find all transporters. - // - n = 0; - aiunit = force->Units; - while (aiunit) { - if (aiunit->Unit->Type->Transporter) { - table[n++] = aiunit->Unit; - } - aiunit = aiunit->Next; - } - - if (!n) { - DebugLevel0Fn("No transporter, lost or error in code?\n"); - force->MustTransport = 0; - force->State = 0; - return; - } - // - // Load all on transporter. - // - f = o = i = 0; - aiunit = force->Units; - while (aiunit) { - Unit *unit; - - unit = aiunit->Unit; - if (!unit->Type->Transporter && unit->Type->UnitType == UnitTypeLand) { - if (!unit->Removed) { - f = 1; - if (unit->Orders[0].Action != UnitActionBoard) { - if (UnitIdle(table[i])) { - DebugLevel0Fn("Send transporter %d\n" _C_ i); - CommandFollow(table[i], unit, FlushCommands); - } - CommandBoard(unit, table[i], FlushCommands); - ++o; - // FIXME - if (o == table[i]->Type->MaxOnBoard) { - DebugLevel0Fn("FIXME: next transporter for AI boarding\n"); - return; - } - } - } - } - aiunit = aiunit->Next; - } - - if (!f) { - DebugLevel0Fn("All are loaded\n"); - ++force->State; - } -} - -/** -** Step 2) -** Send force awaay in transporters, to unload at target position. -** -** @param force Force pointer. -** -** @todo The transporter should avoid enemy contact and should land -** at an unfortified coast. If we send more transporters they -** should land on different positions. -*/ -local void AiSendTransporter(AiForce * force) -{ - AiUnit *aiunit; - - // - // Find all transporters. - // - aiunit = force->Units; - while (aiunit) { - // Transporter to unload units - if (aiunit->Unit->Type->Transporter) { - CommandUnload(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP, - FlushCommands); - // Ships to defend transporter - } else if (aiunit->Unit->Type->UnitType == UnitTypeNaval) { - CommandAttack(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP, - FlushCommands); - } - aiunit = aiunit->Next; - } - ++force->State; -} - -/** -** Step 3) -** Wait for transporters landed. -** -** @param force Force pointer. -** -*/ -local void AiWaitLanded(AiForce * force) -{ - AiUnit *aiunit; - int i; - - DebugLevel0Fn("Waiting\n"); - // - // Find all transporters. - // - i = 1; - aiunit = force->Units; - while (aiunit) { - if (aiunit->Unit->Type->Transporter) { - if (UnitIdle(aiunit->Unit)) { - DebugLevel0Fn("Unloading\n"); - // Don't tell empty transporters to unload. - if (aiunit->Unit->InsideCount) { - CommandUnload(aiunit->Unit, force->GoalX, force->GoalY, - NoUnitP, FlushCommands); - i = 0; - } - } else { - i = 0; - } - } - aiunit = aiunit->Next; - } - if (i) { - ++force->State; // all unloaded - } -} - -/** -** Step 4) -** Force on attack ride. We attack until there is no unit or enemy left. -** -** @param force Force pointer. -*/ -local void AiForceAttacks(AiForce * force) -{ - const AiUnit *aiunit; - - if ((aiunit = force->Units)) { - while (aiunit) { - // Still some action - if (!UnitIdle(aiunit->Unit)) { - break; - } - aiunit = aiunit->Next; - } - // Must mark the attack as terminated - if (!aiunit) { - DebugLevel3Fn("FORCE stopped ( AiForceAttacks, unitactionstill )\n"); - DebugLevel3Fn("force target was %d %d\n" _C_ force->GoalX _C_ force->GoalY); - DebugLevel3Fn("unit pos was %d %d\n" _C_ force->Units->Unit->X _C_ force-> - Units->Unit->Y); - - force->Attacking = 0; - // AiAttackWithForce(force-AiPlayer->Force); - } - } else { - DebugLevel3Fn("FORCE stopped ( AiAttackWithForce, no unit )\n"); - force->Attacking = 0; - } -} - global void AiForceHelpMe(int force, const Unit * attacker, Unit * defender) { AiForce *aiForce; @@ -1223,58 +984,12 @@ global void AiForceHelpMe(int force, const Unit * attacker, Unit * defender) } } -/** -** Handle an attack force. -** -** @param force Force pointer. -*/ -local void AiGuideAttackForce(AiForce * force) -{ - enum { StartState = 1, TransporterLoaded, WaitLanded, AttackNow }; - - switch (force->State) { - // - // Load units on transporters. - // - case StartState: - AiLoadForce(force); - break; - case TransporterLoaded: - AiSendTransporter(force); - break; - case WaitLanded: - AiWaitLanded(force); - break; - case AttackNow: - force->State = 0; - AiAttackWithForce(force - AiPlayer->Force); - break; - - // - // Attacking! - // - case 0: - AiForceAttacks(force); - break; - } -} - /** ** Entry point of force manager, perodic called. */ global void AiForceManager(void) { - int force; - - // - // Look if our defenders still have enemies in range. - // - for (force = 0; force < AI_MAX_FORCES; ++force) { - if (AiPlayer->Force[force].Attacking) { - AiCleanForce(force); - AiGuideAttackForce(&AiPlayer->Force[force]); - } - } + // FIXME : is this really needed anymore AiAssignFreeUnitsToForce(); } diff --git a/src/ai/ai_plan.cpp b/src/ai/ai_plan.cpp index a7bfa3e49..ed38b7bcb 100644 --- a/src/ai/ai_plan.cpp +++ b/src/ai/ai_plan.cpp @@ -50,552 +50,6 @@ -- Variables ----------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------- --- Functions -----------------------------------------------------------------------------*/ - -/** -** Choose enemy on map tile. -** -** @param source Unit which want to attack. -** @param tx X position on map, tile-based. -** @param ty Y position on map, tile-based. -** -** @return Returns ideal target on map tile. -*/ -local Unit *EnemyOnMapTile(const Unit * source, int tx, int ty) -{ - Unit *table[UnitMax]; - Unit *unit; - Unit *best; - const UnitType *type; - int n; - int i; - - n = SelectUnitsOnTile(tx, ty, table); - best = NoUnitP; - for (i = 0; i < n; ++i) { - unit = table[i]; - // unusable unit ? - // if( UnitUnusable(unit) ) can't attack constructions - // FIXME: did SelectUnitsOnTile already filter this? - // Invisible and not Visible - if (unit->Removed || unit->Invisible || !unit->HP - || !(unit->Visible & (1 << source->Player->Player)) - || unit->Orders[0].Action == UnitActionDie) { - continue; - } - type = unit->Type; - if (tx < unit->X || tx >= unit->X + type->TileWidth - || ty < unit->Y || ty >= unit->Y + type->TileHeight) { - continue; - } - if (!CanTarget(source->Type, unit->Type)) { - continue; - } - if (!IsEnemy(source->Player, unit)) { // a friend or neutral - continue; - } - // - // Choose the best target. - // - if (!best || best->Type->Priority < unit->Type->Priority) { - best = unit; - } - } - return best; -} - -/** -** Mark all by transporter reachable water tiles. -** -** @param unit Transporter -** @param matrix Water matrix. -** -** @note only works for water transporters! -*/ -local void AiMarkWaterTransporter(const Unit * unit, unsigned char *matrix) -{ - static const int xoffset[] = { 0, -1, +1, 0, -1, +1, -1, +1 }; - static const int yoffset[] = { -1, 0, 0, +1, -1, -1, +1, +1 }; - struct { - unsigned short X; - unsigned short Y; - } *points; - int size; - int x; - int y; - int rx; - int ry; - int mask; - int wp; - int rp; - int ep; - int i; - int w; - unsigned char *m; - - x = unit->X; - y = unit->Y; - w = TheMap.Width + 2; - matrix += w + w + 2; - if (matrix[x + y * w]) { // already marked - DebugLevel0("Done\n"); - return; - } - - points = malloc(TheMap.Width * TheMap.Height); - size = TheMap.Width * TheMap.Height / sizeof (*points); - - // - // Make movement matrix. - // - mask = UnitMovementMask(unit); - // Ignore all possible mobile units. - mask &= ~(MapFieldLandUnit | MapFieldAirUnit | MapFieldSeaUnit); - - points[0].X = x; - points[0].Y = y; - rp = 0; - matrix[x + y * w] = 66; // mark start point - ep = wp = 1; // start with one point - - // - // Pop a point from stack, push all neightbors which could be entered. - // - for (;;) { - while (rp != ep) { - rx = points[rp].X; - ry = points[rp].Y; - for (i = 0; i < 8; ++i) { // mark all neighbors - x = rx + xoffset[i]; - y = ry + yoffset[i]; - m = matrix + x + y * w; - if (*m) { // already checked - continue; - } - - if (CanMoveToMask(x, y, mask)) { // reachable - - *m = 66; - points[wp].X = x; // push the point - points[wp].Y = y; - if (++wp >= size) { // round about - wp = 0; - } - /* Must be checked multiple - } else { // unreachable - *m=99; - */ - } - } - - if (++rp >= size) { // round about - rp = 0; - } - } - - // - // Continue with next frame. - // - if (rp == wp) { // unreachable, no more points available - break; - } - ep = wp; - } - - free(points); -} - -/** -** Find possible targets. -** -** @param unit Attack. -** @param matrix Water matrix. -** @param dx Attack point X. -** @param dy Attack point Y. -** @param ds Attack state. -** -** @return True if target found. -*/ -local int AiFindTarget(const Unit * unit, unsigned char *matrix, int *dx, int *dy, - int *ds) -{ - static const int xoffset[] = { 0, -1, +1, 0, -1, +1, -1, +1 }; - static const int yoffset[] = { -1, 0, 0, +1, -1, -1, +1, +1 }; - struct { - unsigned short X; - unsigned short Y; - unsigned char State; - } *points; - int size; - int x; - int y; - int rx; - int ry; - int mask; - int wp; - int rp; - int ep; - int i; - int w; - enum { OnWater, OnLand, OnIsle } state; - unsigned char *m; - - size = TheMap.Width * TheMap.Height / 2; - points = malloc(size * sizeof (*points)); - - x = unit->X; - y = unit->Y; - - w = TheMap.Width + 2; - mask = UnitMovementMask(unit); - // Ignore all possible mobile units. - mask &= ~(MapFieldLandUnit | MapFieldAirUnit | MapFieldSeaUnit); - - points[0].X = x; - points[0].Y = y; - points[0].State = OnLand; - matrix += w + w + 2; - rp = 0; - matrix[x + y * w] = 1; // mark start point - ep = wp = 1; // start with one point - - // - // Pop a point from stack, push all neightbors which could be entered. - // - for (;;) { - while (rp != ep) { - rx = points[rp].X; - ry = points[rp].Y; - state = points[rp].State; - for (i = 0; i < 8; ++i) { // mark all neighbors - x = rx + xoffset[i]; - y = ry + yoffset[i]; - m = matrix + x + y * w; - - if (state != OnWater) { - if (*m) { // already checked - if (state == OnLand && *m == 66) { // tansporter? - DebugLevel0Fn("->Water\n"); - *m = 6; - points[wp].X = x; // push the point - points[wp].Y = y; - points[wp].State = OnWater; - if (++wp >= size) { // round about - wp = 0; - } - } - continue; - } - // Check targets on tile? - // FIXME: the move code didn't likes a shore building as - // target - if (EnemyOnMapTile(unit, x, y)) { - DebugLevel0Fn("Target found %d,%d-%d\n" _C_ x _C_ y _C_ state); - *dx = x; - *dy = y; - *ds = state; - free(points); - return 1; - } - - if (CanMoveToMask(x, y, mask)) { // reachable - - *m = 1; - points[wp].X = x; // push the point - points[wp].Y = y; - points[wp].State = state; - if (++wp >= size) { // round about - wp = 0; - } - } else { // unreachable - *m = 99; - } - } else { // On water - if (*m) { // already checked - if (*m == 66) { // tansporter? - *m = 6; - points[wp].X = x; // push the point - points[wp].Y = y; - points[wp].State = OnWater; - if (++wp >= size) { // round about - wp = 0; - } - } - continue; - } - if (CanMoveToMask(x, y, mask)) { // reachable - DebugLevel0Fn("->Land\n"); - *m = 1; - points[wp].X = x; // push the point - points[wp].Y = y; - points[wp].State = OnIsle; - if (++wp >= size) { // round about - wp = 0; - } - } else { // unreachable - *m = 99; - } - } - } - - if (++rp >= size) { // round about - rp = 0; - } - } - - // - // Continue with next frame. - // - if (rp == wp) { // unreachable, no more points available - break; - } - ep = wp; - } - free(points); - return 0; -} - -/** -** Find possible walls to target. -** -** @param force Attack force. -** -** @return True if wall found. -*/ -global int AiFindWall(AiForce * force) -{ - static const int xoffset[] = { 0, -1, +1, 0, -1, +1, -1, +1 }; - static const int yoffset[] = { -1, 0, 0, +1, -1, -1, +1, +1 }; - struct { - unsigned short X; - unsigned short Y; - } *points; - int size; - int x; - int y; - int rx; - int ry; - int mask; - int wp; - int rp; - int ep; - int i; - int w; - unsigned char *m; - unsigned char *matrix; - int destx; - int desty; - AiUnit *aiunit; - Unit *unit; - - // Find a unit to use. Best choice is a land unit with range 1. - // Next best choice is any land unit. Otherwise just use the first. - aiunit = force->Units; - unit = aiunit->Unit; - while (aiunit) { - if (aiunit->Unit->Type->UnitType == UnitTypeLand) { - unit = aiunit->Unit; - if (aiunit->Unit->Type->Missile.Missile->Range == 1) { - break; - } - } - aiunit = aiunit->Next; - } - - x = unit->X; - y = unit->Y; - size = TheMap.Width * TheMap.Height / 4; - points = malloc(size * sizeof (*points)); - - destx = -1; - desty = -1; - - matrix = CreateMatrix(); - w = TheMap.Width + 2; - matrix += w + w + 2; - - points[0].X = x; - points[0].Y = y; - rp = 0; - matrix[x + y * w] = 1; // mark start point - ep = wp = 1; // start with one point - - mask = UnitMovementMask(unit); - - // - // Pop a point from stack, push all neighbors which could be entered. - // - for (; destx == -1;) { - while (rp != ep && destx == -1) { - rx = points[rp].X; - ry = points[rp].Y; - for (i = 0; i < 8; ++i) { // mark all neighbors - x = rx + xoffset[i]; - y = ry + yoffset[i]; - m = matrix + x + y * w; - if (*m) { - continue; - } - // - // Check for a wall - // - if (WallOnMap(x, y)) { - DebugLevel0Fn("Wall found %d,%d\n" _C_ x _C_ y); - destx = x; - desty = y; - break; - } - - if (CanMoveToMask(x, y, mask)) { // reachable - *m = 1; - points[wp].X = x; // push the point - points[wp].Y = y; - if (++wp >= size) { // round about - wp = 0; - } - } else { // unreachable - *m = 99; - } - } - if (++rp >= size) { // round about - rp = 0; - } - } - - // - // Continue with next frame. - // - if (rp == wp) { // unreachable, no more points available - break; - } - ep = wp; - } - free(points); - - if (destx != -1) { - force->State = 0; - aiunit = force->Units; - while (aiunit) { - if (aiunit->Unit->Type->CanAttack) { - CommandAttack(aiunit->Unit, destx, desty, NULL, FlushCommands); - } else { - CommandMove(aiunit->Unit, destx, desty, FlushCommands); - } - aiunit = aiunit->Next; - } - return 1; - } - - return 0; -} - -/** -** Plan an attack with a force. -** We know, that we must use a transporter. -** -** @param force Pointer on the force. -** -** @return True if target found, false otherwise. -** -** @todo Perfect planning. -** Only works for water transporter! -*/ -global int AiPlanAttack(AiForce * force) -{ - char *watermatrix; - const AiUnit *aiunit; - int x; - int y; - int i; - int state; - Unit *transporter; - - DebugLevel0Fn("Planning for force #%d of player #%d\n" - _C_ force - AiPlayer->Force _C_ AiPlayer->Player->Player); - - watermatrix = CreateMatrix(); - - // - // Transporter must be already assigned to the force. - // NOTE: finding free transportes was too much work for me. - // - aiunit = force->Units; - state = 1; - while (aiunit) { - if (aiunit->Unit->Type->Transporter) { - DebugLevel0Fn("Transporter #%d\n" _C_ UnitNumber(aiunit->Unit)); - AiMarkWaterTransporter(aiunit->Unit, watermatrix); - state = 0; - } - aiunit = aiunit->Next; - } - - // - // No transport that belongs to the force. - // - transporter = NULL; - if (state) { - for (i = 0; i < AiPlayer->Player->TotalNumUnits; ++i) { - Unit *unit; - - unit = AiPlayer->Player->Units[i]; - if (unit->Type->Transporter && UnitIdle(unit)) { - DebugLevel0Fn("Assign any transporter\n"); - AiMarkWaterTransporter(unit, watermatrix); - // FIXME: can be the wrong transporter. - transporter = unit; - state = 0; - } - } - } - - if (state) { // Absolute no transporter - DebugLevel0Fn("No transporter available\n"); - // FIXME: should tell the resource manager we need a transporter! - return 0; - } - // - // Find a land unit of the force. - // FIXME: if force is split over different places -> broken - // - aiunit = force->Units; - while (aiunit) { - if (aiunit->Unit->Type->UnitType == UnitTypeLand) { - DebugLevel0Fn("Landunit %d\n" _C_ UnitNumber(aiunit->Unit)); - break; - } - aiunit = aiunit->Next; - } - - if (!aiunit) { - DebugLevel0Fn("No land unit in force\n"); - return 0; - } - - if (AiFindTarget(aiunit->Unit, watermatrix, &x, &y, &state)) { - AiUnit *aiunit; - - if (transporter) { - aiunit = malloc(sizeof (*aiunit)); - aiunit->Next = force->Units; - force->Units = aiunit; - aiunit->Unit = transporter; - RefsIncrease(transporter); - } - - DebugLevel0Fn("Can attack\n"); - force->GoalX = x; - force->GoalY = y; - force->MustTransport = state == 2; - - force->State = 1; - return 1; - } - return 0; -} - /** ** Respond to ExplorationRequests */ diff --git a/src/ai/script_ai.cpp b/src/ai/script_ai.cpp index 26c186038..8f2fe0df0 100644 --- a/src/ai/script_ai.cpp +++ b/src/ai/script_ai.cpp @@ -1528,25 +1528,6 @@ local SCM CclAiWaitForce(SCM value) return SCM_BOOL_T; } -/** -** Attack with force. -** -** @param value Force number. -*/ -local SCM CclAiAttackWithForce(SCM value) -{ - int force; - - force = gh_scm2int(value); - if (force < 0 || force >= AI_MAX_FORCES) { - errl("Force out of range", value); - } - - AiAttackWithForce(force); - - return SCM_BOOL_F; -} - /** ** Attack with force, on the current script hotspot. ** @@ -2110,7 +2091,6 @@ global void AiCclRegister(void) gh_new_procedure0_0("ai:idle", CclAiIdle); gh_new_procedure2_0("ai:timed-wait-force", CclAiTimedWaitForce); - gh_new_procedure1_0("ai:attack-with-force", CclAiAttackWithForce); gh_new_procedure1_0("ai:hotspot-attack-with-force", CclAiHotSpotAttackWithForce); gh_new_procedure1_0("ai:force-go-home", CclAiForceHome); gh_new_procedure1_0("ai:sleep", CclAiSleep);