eqemu-server/zone/embperl.h
hg e01ac39887
[Quest API] Send delivered task items in trade events (#2518)
This restores sending items to EVENT_TRADE that are updated by source
controlled delivery tasks which was removed in 7cf96ca2d8.

That patch filtered out items consumed by task updates to fix a few bugs
with items being returned despite incrementing a task:

  - If an npc without a quest trade event handler was the target of a
    delivery task for a NoDrop/non-Quest item, the npc would auto return
    it due to the `ReturnNonQuestNoDropItems` rule.

  - If an npc without a quest trade event handler was the target of a
    delivery task for a non-NoDrop item, the item would be added to the
    npc's loot.

  - If an npc with an EVENT_ITEM/EVENT_TRADE quest handler used the Lua
    or Perl trade plugins, the plugins would return task items unless
    specific checks for the turned in slots existed.

The quest plugin item returns are problematic for this since they just
summon to return items not handled by the script

  e.g. For a task to deliver N Large Fruit Bat Wings (item id 19616),
  if a player turned in 1 Wing in slot 1 and a stack of 20 Wings in slot
  2, the task would be incremented 21 times and the following Lua trade
  handler would return the stack of 20 from the 2nd trade slot:

  ```lua
    function event_trade(e)
      local item_lib = require("items")
      if item_lib.check_turn_in(e.trade, { item1 = 19616 }) then
        eq.debug("Lua consumed 1 slot and will return other slots")
      end
      item_lib.return_items(e.self, e.other, e.trade)
    end
  ```

  This also occured with the perl plugin though slightly differently
  since that plugin returns all slots unless the exact handin slot count
  matches (requiring check_handin conditions for all slots):

  ```perl
    sub EVENT_ITEM {
      if (plugin::check_handin(\%itemcount, 19616 => 1)) {
        # No issue if only one slot used for trade (item not returned)
      }
      # Perl fails handin check if multiple slots not checked and returns all
      plugin::return_items(\%itemcount);
    }
  ```

While that patch solved the issue, it's inconvenient and wrong to not
receive items in trade events used in a source task update. It breaks
existing trade scripts for tasks that aren't quest controlled and it
forces tasks to be changed to quest controlled and manually updated to
script any extra behavior.

This patch stores the task update count on the item instance before
dispatching it to quests. The burden is now on quests and plugins to
use that value in order to prevent returning items consumed by tasks.

`ItemInstance::RemoveTaskDeliveredItems` has been added to simplify
handling this in plugins which is also used for non-quest item returns.
2022-11-06 17:10:30 -05:00

175 lines
4.6 KiB
C++

/*
Embperl.h
---------------
eqemu perl wrapper
Eglin
*/
#ifndef EMBPERL_H
#define EMBPERL_H
#ifdef EMBPERL
#include "zone_config.h"
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include <string.h>
// this option disables distinct int/float/string function argument types for
// backwards compatibility with current perl api usage
// e.g. quest::settimer(0, 1) using number for timer name instead of string
#define PERLBIND_NO_STRICT_SCALAR_TYPES
#include <perlbind/perlbind.h>
namespace perl = perlbind;
#ifdef WIN32
#define snprintf _snprintf
#endif
//perl defines these macros and dosent clean them up, lazy bastards. -- I hate them too!
#ifdef Copy
#undef Copy
#endif
#ifdef list
#undef list
#endif
#ifdef write
#undef write
#endif
#ifdef bool
#undef bool
#endif
#ifdef Zero
#undef Zero
#endif
//These need to be cleaned up on FreeBSD
#ifdef __FreeBSD__
#ifdef do_open
#undef do_open
#endif
#ifdef do_close
#undef do_close
#endif
#endif
//so embedded scripts can use xs extensions (ala 'use socket;')
EXTERN_C void boot_DynaLoader(pTHX_ CV* cv);
EXTERN_C void xs_init(pTHX);
extern const ZoneConfig *Config;
class Embperl
{
private:
//if we fail inside a script evaluation, this will hold the croak msg (not much help if we die during construction, but that's our own fault)
mutable std::string errmsg;
//kludgy workaround for the fact that we can't directly do something like SvIV(get_sv($big[0]{ass}->{struct}))
SV * my_get_sv(const char * varname) {
char buffer[256];
snprintf(buffer, 256, "if(defined(%s)) { $scratch::temp = %s; } else { $scratch::temp = 'UNDEF'; }", varname, varname);
eval(buffer);
return get_sv("scratch::temp", false);
}
//install a perl func
void init_eval_file(void);
bool in_use; //true if perl is executing
protected:
//the embedded interpreter
PerlInterpreter * my_perl;
void DoInit();
public:
Embperl(void); //This can throw errors! Buyer beware
~Embperl(void);
void Reinit();
//return the last error msg
std::string lasterr(void) const { return errmsg;};
//evaluate an expression. throws string errors on fail
int eval(const char * code);
//execute a subroutine. throws lasterr on failure
int dosub(const char * subname, const std::vector<std::string> * args = nullptr, int mode = G_SCALAR|G_EVAL);
//Access to perl variables
//all varnames here should be of the form package::name
//returns the contents of the perl variable named in varname as a c int
int geti(const char * varname) { return static_cast<int>(SvIV(my_get_sv(varname))); };
//returns the contents of the perl variable named in varname as a c float
float getd(const char * varname) { return static_cast<float>(SvNV(my_get_sv(varname)));};
//returns the contents of the perl variable named in varname as a string
std::string getstr(const char * varname) {
SV * temp = my_get_sv(varname);
return std::string(SvPV_nolen(temp),SvLEN(temp));
}
//put an integer into a perl varable
void seti(const char *varname, int val) const {
SV *t = get_sv(varname, true);
sv_setiv(t, val);
}
//put a real into a perl varable
void setd(const char *varname, float val) const {
SV *t = get_sv(varname, true);
sv_setnv(t, val);
}
//put a string into a perl varable
void setstr(const char *varname, const char *val) const {
SV *t = get_sv(varname, true);
sv_setpv(t, val);
}
// put a pointer into a blessed perl variable
void setptr(const char* varname, const char* classname, void* val) const {
SV* t = get_sv(varname, GV_ADD);
sv_setref_pv(t, classname, val);
}
// put key-value pairs in hash
void sethash(const char *varname, std::map<std::string,std::string> &vals)
{
std::map<std::string,std::string>::iterator it;
// Get hash and clear it.
HV *hv = get_hv(varname, TRUE);
hv_clear(hv);
// Iterate through key-value pairs, storing them in hash
for (it = vals.begin(); it != vals.end(); ++it)
{
int keylen = static_cast<int>(it->first.length());
SV *val = newSVpv(it->second.c_str(), it->second.length());
// If val was not added to hash, reset reference count
if (hv_store(hv, it->first.c_str(), keylen, val, 0) == nullptr)
val->sv_refcnt = 0;
}
}
//loads a file and compiles it into our interpreter (assuming it hasn't already been read in)
//idea borrowed from perlembed
int eval_file(const char * packagename, const char * filename);
inline bool InUse() const { return(in_use); }
//check to see if a sub exists in package
bool SubExists(const char *package, const char *sub);
//check to see if a variable exists in package
bool VarExists(const char *package, const char *var);
};
#endif //EMBPERL
#endif //EMBPERL_H