I have developed a stable algorithm principally for use in LIVAN, but could concievably be used in IVAN, which enables the AI to throw certain items at hostile creatures in the game. It is the result of several hours of sourcediving and general fooling around with the code.
The algorithm itself is straightforward:
- The thrower has to be humanoid, among other things.
- Has to have at least one arm
- Has to be a ranged attacker (this is new)
- First he checks the area for hostiles he can see:
He looks in all eight directions for a suitable throwing direction, free from friendlies and a direction that contains the nearest hostile. The status of the directions are stored in a buffer; they will either be ZERO, SUCCESS or BLOCKED.
- Once he has checked out the whole area, and has a SUCCESS direction, he checks his inventory for a suitable throwing weapon. The throwing weapons have been implemented as AXE and DAGGER in this case.
- If he has something suitable to throw, he will throw that item in the direction of the hostile he picked up on his radar
Now for practical purposes, the thrower has been implemented as a novel dwarf type character, "axethrowerdwarf", resembling a kamakaze dwarf, but without the backpack.
There is also a modification to the "CheckForUsefulItemsOnGround", where the dwarf will pick up suitable objects he can throw, and hoards them in his inventory for later use. That way he can re-stock his ammo.
The axethrower dwarf may seem a little underpowered, however, this algorithm is designed to provide scope for anyone who wants to create the abominable "Grenadier Dwarves" that have been mentioned somewhere...
That would require a simple change to some of the gas-grenade item-properties in the script, and perhaps a change to the Grenadier Dwarf's initial equipment :E
I hope I have catalogued everything that is required, let me know if it doesn't compile. Code additions are in bold-faced type. Here it is:
[color=#9acd32]// To make the AI throw items, add the following lines of code:
// In human.h:[/color]
CHARACTER(humanoid, character)
{
public:
...
[b]virtual truth CheckThrowItemOpportunity();[/b]
...
}
[b]CHARACTER(axethrowerdwarf, humanoid)
{
public:
virtual void GetAICommand();
virtual void CreateInitialEquipment(int);
protected:
virtual int GetTorsoMainColor() const;
virtual int GetGauntletColor() const;
virtual int GetLegMainColor() const;
virtual v2 GetDrawDisplacement(int) const;
virtual int GetWSkillHits() const { return 10000; }
virtual truth IsElite() const { return false; }
};[/b]
[color=#9acd32]// In human.cpp:[/color]
[b]
void axethrowerdwarf::CreateInitialEquipment(int SpecialFlags)
{
SetRightWielded(meleeweapon::Spawn(AXE, SpecialFlags));
SetLeftWielded(meleeweapon::Spawn(AXE, SpecialFlags));
for(int k = 0; k < 6; k++)
{
GetStack()->AddItem(meleeweapon::Spawn(AXE));
}
GetStack()->AddItem(lantern::Spawn());
GetCWeaponSkill(AXES)->AddHit(GetWSkillHits());
GetCurrentRightSWeaponSkill()->AddHit(GetWSkillHits());
}
void axethrowerdwarf::GetAICommand()
{
if(CheckThrowItemOpportunity())
return;
character::GetAICommand();
}
v2 axethrowerdwarf::GetDrawDisplacement(int j) const
{
static v2 DrawDisplacement[] = { v2(0, 0), v2(0, 1), v2(0, -1), v2(0, -1), v2(0, -1), v2(0, 0), v2(0, 0) };
return DrawDisplacement[j];
}
col16 axethrowerdwarf::GetTorsoMainColor() const
{
return GetMasterGod()->GetColor();
}
col16 axethrowerdwarf::GetGauntletColor() const
{
return GetMasterGod()->GetColor();
}
col16 axethrowerdwarf::GetLegMainColor() const
{
return GetMasterGod()->GetColor();
}
truth humanoid::CheckThrowItemOpportunity()
{
if(!HasAUsableArm())
return false;
else
return character::CheckThrowItemOpportunity();
}
[/b]
[color=#9acd32]// In char.dat:[/color]
character
{
...
[b]IsRangedAttacker = false;[/b]
}
[b]axethrowerdwarf
{
DefaultArmStrength = 20;
DefaultLegStrength = 20;
DefaultDexterity = 20;
DefaultAgility = 10;
DefaultEndurance = 12;
DefaultPerception = 18;
DefaultIntelligence = 10;
DefaultWisdom = 5;
DefaultCharisma = 10;
DefaultMana = 10;
HeadBitmapPos = 112, 160;
TorsoBitmapPos = 48, 16;
ArmBitmapPos = 80, 0;
LegBitmapPos = 0, 208;
HairColor = rgb16(144, 72, 0);
BeltColor = rgb16(72, 56, 16);
TotalVolume = 50000;
TotalSize = 100;
CanRead = true;
NameSingular = "axethrower dwarf";
NamePlural = "axethrower dwarves";
CanBeGenerated = true;
CreateDivineConfigurations = true;
IsAbstract = true;
/* Equipment initialization is overridden */
PanicLevel = 1;
Inventory == IRON meleeweapon(AXE) {Times = 6;}
FleshMaterial = DWARF_FLESH;
DeathMessage = "@Dd dies in agony.";
IgnoreDanger = true;
Frequency = 300;
HostileReplies == "\\"In the name of @Gd I will slash you to pieces!\\"";
AttachedGod = NONE;
UndeadVersions = false;
WieldedPosition = -1, -2;
FriendlyReplies =
{
4,
"\\"Would you like me to teach you the best way to slice tomatoes?\\"",
"@Dd shouts: \\"Death to disbelievers!\\"",
"@Dd praises @Gd with numerous hymns. @Pp is obviously a very devoted follower.",
"\\"One day, Holy War will break out, hopefully I will earn my backpack by then!\\"";
}
IsRangedAttacker = true;
KnownCWeaponSkills == AXES;
CWeaponSkillHits == 100;
RightSWeaponSkillHits = 50;
UsesLongArticle = true;
}
[/b]
[color=#9acd32]// In database.cpp:[/color]
template<> void databasecreator<character>::CreateDataBaseMemberMap()
{
...
[b]ADD_MEMBER(IsRangedAttacker);[/b]
}
template<> void databasecreator<item>::CreateDataBaseMemberMap()
{
...
[b]ADD_MEMBER(IsThrowingWeapon);[/b]
}
[color=#9acd32]// In item.dat:[/color]
item
{
...
[b]IsThrowingWeapon = false;[/b]
}
Config AXE;
{
...
[b]IsThrowingWeapon = true;[/b]
}
Config DAGGER; // if you like throwing daggers :E
{
...
[b]IsThrowingWeapon = true;[/b]
}
[color=#9acd32]// In item.h:[/color]
struct itemdatabase : public databasebase
{
...
[b]truth IsThrowingWeapon;[/b]
};
class item : public object
{
...
[b]DATA_BASE_TRUTH(IsThrowingWeapon);[/b]
...
}
[color=#9acd32]// In char.h:[/color]
struct characterdatabase : public databasebase
{
...
[b]truth IsRangedAttacker;[/b]
};
class character : public entity, public id
{
public:
...
[b]truth TryToAddToInventory(item*);[/b]
...
[b]DATA_BASE_TRUTH(IsRangedAttacker);[/b]
...
#endif
...
[b]truth CheckThrowItemOpportunity();[/b]
...
}
[color=#9acd32]// In char.cpp[/color]
truth character::CheckForUsefulItemsOnGround(truth CheckFood)
{
...
for(uint c = 0; c < ItemVector.size(); ++c)
if(ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this))
{
if(!(CommandFlags & DONT_CHANGE_EQUIPMENT)
&& TryToEquip(ItemVector[c]))
return true;
if(CheckFood && UsesNutrition() && !CheckIfSatiated()
&& TryToConsume(ItemVector[c]))
return true;
[b]
if( (ItemVector[c])->IsThrowingWeapon() && IsRangedAttacker()
&& TryToAddToInventory(ItemVector[c]))
return true; [/b]
}
return false;
}
[b]
truth character::TryToAddToInventory(item* Item)
{
if(!(GetBurdenState() > STRESSED)
|| !CanUseEquipment()
|| Item->GetSquaresUnder() != 1)
return false;
room* Room = GetRoom();
if(!Room || Room->PickupItem(this, Item, 1))
{
if(CanBeSeenByPlayer())
ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
Item->MoveTo(GetStack());
DexterityAction(5);
return true;
}
return false;
}
[/b]
[color=#9acd32]//finally, the algorithm itself[/color]
[b]
truth character::CheckThrowItemOpportunity()
{
if(!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) // total gum
return false;
// Steps:
// (1) - Acquire target as nearest enemy
// (2) - Check that this enemy is in range, and is in appropriate direction
// No friendly fire!
// (3) - check inventory for throwing weapon, select this weapon
// (4) - throw item in direction where the enemy is
//Check the visible area for hostiles
v2 Pos = GetPos();
v2 TestPos;
int RangeMax = GetLOSRange();
int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
int HostileFound = 0;
for(int r = 1; r <= RangeMax; ++r)
{
for(int dir = 0; dir < 8; ++dir)
{
switch(dir)
{
case 0:
TestPos = v2(Pos.X - r, Pos.Y - r);
break;
case 1:
TestPos = v2(Pos.X, Pos.Y - r);
break;
case 2:
TestPos = v2(Pos.X + r, Pos.Y - r);
break;
case 3:
TestPos = v2(Pos.X - r, Pos.Y);
break;
case 4:
TestPos = v2(Pos.X + r, Pos.Y);
break;
case 5:
TestPos = v2(Pos.X - r, Pos.Y + r);
break;
case 6:
TestPos = v2(Pos.X, Pos.Y + r);
break;
case 7:
TestPos = v2(Pos.X + r, Pos.Y + r);
break;
}
level* Level = GetLevel();
if(Level->IsValidPos(TestPos))
{
square* TestSquare = GetNearSquare(TestPos);
character* Dude = TestSquare->GetCharacter();
if((Dude && Dude->IsEnabled() && (GetRelation(Dude) != HOSTILE)
&& Dude->CanBeSeenBy(this, false, true)))
{
CandidateDirections[dir] = BLOCKED;
}
if(Dude && Dude->IsEnabled() && (GetRelation(Dude) == HOSTILE)
&& Dude->CanBeSeenBy(this, false, true)
&& (CandidateDirections[dir] != BLOCKED))
{
//then load this candidate position direction into the vector of possible throw directions
CandidateDirections[dir] = SUCCESS;
HostileFound = 1;
}
}
}
}
int ThrowDirection = 0;
int TargetFound = 0;
if(HostileFound)
{
for(int dir = 0; dir < 8; ++dir)
{
if( (CandidateDirections[dir] == SUCCESS) && !TargetFound)
{
ThrowDirection = dir;
TargetFound = 1;
}
}
if(!TargetFound)
{
return false;
}
}
else
return false;
//check inventory for throwing weapon
itemvector ItemVector;
GetStack()->FillItemVector(ItemVector);
item* ToBeThrown = 0;
for(uint c = 0; c < ItemVector.size(); ++c)
if(ItemVector[c]->IsThrowingWeapon())
{
ToBeThrown = ItemVector[c];
break;
}
if(!ToBeThrown)
return false;
if(CanBeSeenByPlayer())
ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
ThrowItem(ThrowDirection, ToBeThrown);
EditExperience(ARM_STRENGTH, 75, 1 << 8);
EditExperience(DEXTERITY, 75, 1 << 8);
EditExperience(PERCEPTION, 75, 1 << 8);
EditNP(-50);
DexterityAction(5);
TerminateGoingTo();
return true;
}
[/b]
[color=#9acd32]// the end[/color]