Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some vanilla behavior for npcs #653

Merged
merged 9 commits into from
Jul 24, 2024
11 changes: 10 additions & 1 deletion game/game/gamescript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,11 @@ void GameScript::initCommon() {

auto* vtime = vm.find_symbol_by_name("VIEW_TIME_PER_CHAR");
viewTimePerChar = vtime != nullptr ? vtime->get_float() : 550.f;
ItKE_lockpick = vm.find_symbol_by_name("ItKE_lockpick");
if(viewTimePerChar<=0.f)
viewTimePerChar = 550.f;

ItKE_lockpick = vm.find_symbol_by_name("ItKE_lockpick");
B_RefreshAtInsert = vm.find_symbol_by_name("B_RefreshAtInsert");
} else {
itMi_Gold = vm.find_symbol_by_name("ItMiNugget");
if(itMi_Gold!=nullptr) { // FIXME
Expand Down Expand Up @@ -1128,6 +1130,13 @@ void GameScript::invokePickLock(Npc& npc, int bSuccess, int bBrokenOpen) {
vm.call_function<void>(fn, bSuccess, bBrokenOpen);
}

void GameScript::invokeRefreshAtInsert(Npc& npc) {
if(B_RefreshAtInsert==nullptr)
return;
ScopeVar self(*vm.global_self(), npc.handlePtr());
vm.call_function<void>(B_RefreshAtInsert);
}

CollideMask GameScript::canNpcCollideWithSpell(Npc& npc, Npc* shooter, int32_t spellId) {
auto fn = vm.find_symbol_by_name("C_CanNpcCollideWithSpell");
if(fn==nullptr)
Expand Down
2 changes: 2 additions & 0 deletions game/game/gamescript.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class GameScript final {
void invokeSpell(Npc& npc, Npc *target, Item& fn);
int invokeCond (Npc& npc, std::string_view func);
void invokePickLock(Npc& npc, int bSuccess, int bBrokenOpen);
void invokeRefreshAtInsert(Npc& npc);
auto canNpcCollideWithSpell(Npc& npc, Npc* shooter, int32_t spellId) -> CollideMask;

int playerHotKeyScreenMap(Npc& pl);
Expand Down Expand Up @@ -460,6 +461,7 @@ class GameScript final {
QuestLog quests;
zenkit::DaedalusSymbol* itMi_Gold = nullptr;
zenkit::DaedalusSymbol* ItKE_lockpick = nullptr;
zenkit::DaedalusSymbol* B_RefreshAtInsert = nullptr;
float tradeValMult = 0.3f;
zenkit::DaedalusSymbol* spellFxInstanceNames = nullptr;
zenkit::DaedalusSymbol* spellFxAniLetters = nullptr;
Expand Down
8 changes: 3 additions & 5 deletions game/game/inventory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -905,13 +905,9 @@ void Inventory::invalidateCond(Item *&slot, Npc &owner) {
}
}

void Inventory::autoEquip(Npc &owner) {
sortItems();

auto a = bestArmour (owner);
void Inventory::autoEquipWeapons(Npc &owner) {
auto m = bestMeleeWeapon(owner);
auto r = bestRangeWeapon(owner);
setSlot(armour,a,owner,false);
setSlot(melee ,m,owner,false);
setSlot(range ,r,owner,false);
}
Expand Down Expand Up @@ -950,6 +946,8 @@ Item* Inventory::bestItem(Npc &owner, ItmFlags f) {
continue;
if(!i->checkCond(owner))
continue;
if(itData.munition>0 && findByClass(size_t(itData.munition))==nullptr)
continue;
Try marked this conversation as resolved.
Show resolved Hide resolved

if(itData.value>g){
ret=i.get();
Expand Down
2 changes: 1 addition & 1 deletion game/game/inventory.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class Inventory final {
void unequip(Item* cls, Npc &owner);
void invalidateCond(Npc &owner);
bool isChanged() const { return !sorted; }
void autoEquip(Npc &owner);
void autoEquipWeapons(Npc &owner);
void equipArmour (int32_t cls, Npc &owner);
void equipBestArmour (Npc &owner);
void equipBestMeleeWeapon(Npc &owner);
Expand Down
28 changes: 19 additions & 9 deletions game/world/objects/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ Npc::Npc(World &owner, size_t instance, std::string_view waypoint)

owner.script().initializeInstanceNpc(hnpc, instance);
hnpc->wp = std::string(waypoint);

// vanilla behavior: equip best weapon and set non-zero damage type
if(!isMonster() && !isPlayer())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more question: isMonster is truly relevant? For wolfs/snappers should not mater; but maybe relevant for skeletons

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes best weapon autoequip works for skeletons and from what I tested it seems to only work for skeletons. Other monsters just have the weapon in their inventory.

For like a wolf weapon inserted with CreateInvItem is equipped and visible in OpenGothic, that's why I excluded monsters in general.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some thinking it's maybe best to prevent weapon equipping for monsters somewhere in the equip function directly in case someone adds a EquipItem call in script.

Skeleton is detected based on guild in vanilla. Changing the guild of a golem to skeleton resulted in a draw/remove weapon stall.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it not directly related to isMonster would be nice to at least understand how it intended to work.
I've done some testing with marvin - guild by itself seem to be irrelevant. Maybe it's related to npc having some builtin bones, such as ZS_SWORD/ZS_RIGHTHAND ?

Can you test what will happen if you assign GIL_SHEEP to skeleton or "Ske_Body" to sheep, while having weapon in inventory?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPD:
Tested by editing Nikolas guild: as GIL_SKELETON or GIL_SHEEP he still equips best weapon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GIL_SHEEP to skeleton

Nothing.

"Ske_Body" to sheep

Nothing but if Mdl_SetVisual (self, "HumanS.mds") with Sheep_Body sword is visible.

For default sheep both Npc_HasEquippedWeapon and Npc_HasEquippedMeleeWeapon return true.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GIL_SHEEP to skeleton
Nothing.

You mean nothing changed, or skeleton does not equip weapons any more?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing changed.

invent.autoEquipWeapons(*this);
if(hnpc->damage_type==0)
hnpc->damage_type = 2;
}

Npc::~Npc(){
Expand Down Expand Up @@ -432,15 +438,12 @@ float Npc::angleDir(float x, float z) {
}

bool Npc::resetPositionToTA() {
const bool g2 = owner.version().game==2;
const bool isDead = this->isDead();
const bool g2 = owner.version().game==2;
const bool isDragon = (g2 && guild()==GIL_DRAGON);
const bool isDead = this->isDead();

if(isDead && !invent.hasMissionItems()) {
const bool isDragon = (g2 && guild()==GIL_DRAGON);
const bool isBackgroundBody = (hnpc->attribute[ATR_HITPOINTSMAX]==1);
if(!isBackgroundBody && !isDragon)
return false;
}
if(isDead && !invent.hasMissionItems() && !isDragon)
return false;

invent.clearSlot(*this,"",currentInteract!=nullptr);
if(!isPlayer())
Expand Down Expand Up @@ -471,8 +474,15 @@ bool Npc::resetPositionToTA() {
setDirection(at->dirX,at->dirY,at->dirZ);
owner.script().fixNpcPosition(*this,0,0);

if(!isDead)
if(!isDead) {
attachToPoint(at);
if(!isMonster())
invent.autoEquipWeapons(*this);
}

if(g2)
owner.script().invokeRefreshAtInsert(*this);
Try marked this conversation as resolved.
Show resolved Hide resolved

return true;
}

Expand Down