Author Topic: Persistent merchants?  (Read 997 times)

Legacy_Surek

  • Full Member
  • ***
  • Posts: 169
  • Karma: +0/-0
Persistent merchants?
« on: April 24, 2015, 05:44:25 am »


               

I was hoping I could get some help. I wanted to know if anyone knows where I could find a simple and easy to use persistent merchant script. that will save the merchant's inventory on server resets. I need it to use the Bioware database. I checked the vault and really did not see anything. I saw lots of persistent storage chest systems but not merchant systems.


Or if I knew what script functions to use maybe I could come up with something on my own.

Any help with this would be greatly appreciated.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Persistent merchants?
« Reply #1 on: April 24, 2015, 10:48:05 am »


               

I've done this in the past:


 


OnOpenStore


OnStoreClosed


These are the events you need to worry about.


 


To put this into pseudocode:


onStoreOpen:

Loop through all present items in the merchants inventory: Delete (to clean the inventory out before spawning our items)


Pull back your 'storage object' from the database.

(Typically this would be an invisible creature or NPC that spawns beside the player/merchant)


Loop through his inventory, and move his items to the merchant inventory.


Destroy the Storage Object


 


onStoreClose:


Create a new Storage Object


Destroy any randomly created items that may have gone into the inventory. (potions, slings etc)


Loop through the inventory of the merchant

Move items from the Merchant to the Storage Objects inventory.

Store the Storage object to the database using an identifier that is linked to the tag of the merchant - overwriting any previous storage object in the database for that merchant.

Delete the storage object

Delete the items from the merchant as well to keep the module clean and fast.


 


The one I did used nwnx_odbc for the database stuff in a mysql db, but this is all do-able in bioware db too.

Just means that your server might jitter a bit when storing or retrieving database objects.



               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #2 on: April 24, 2015, 06:07:45 pm »


               

onStoreClose:

Create a new Storage Object


Destroy any randomly created items that may have gone into the inventory. (potions, slings etc)


Loop through the inventory of the merchant

Move items from the Merchant to the Storage Objects inventory.




 


Caution is needed here.  Objects are not destroyed until after the script is completed.  (DestroyObject() inherently has DelayCommand() built into it).


               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Persistent merchants?
« Reply #3 on: April 25, 2015, 04:05:49 pm »


               

True, 


but given that we are just destroying everything on the creature that should not be there, its not too risky.


We could always do a delay command between the destroy and the move  and store.


To ensure that we have given enough time for the items to be destroyed.


 


Alternatively, if you have a trash cleanup system in place, like a bin system, you could always 'move' the items to an other inventory - moving items is instant.


Eg:


object oBin = BLAH


AssignCommand(oBin,TakeItem()); // this sort of thing


 


Its somewhat of a bad practice to do:


but worst case scenario :


you could just create a wrapper function to turn DestroyObject into a non-delay type method.


 


Eg:


 


 


 
void DestroySingleThreaded(object o)
{
    DestroyObject(o,0.01);
    while(o != OBJECT_INVALID)
    {
         
    }
    
}
 
 

 


I am sure there are more efficient ways of doing it, but you could call this method, in order to remove the threat of the delay causing items to slip through the cracks.



               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Persistent merchants?
« Reply #4 on: April 25, 2015, 06:03:23 pm »


               

Are you sure... that method looks like it will just run until TMI and then destroy the object after it's done. NWN is single threaded.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Persistent merchants?
« Reply #5 on: April 25, 2015, 08:10:05 pm »


               Yes NWN is single threaded at its lowest level, but with the delay type methods, they in effect simulate running on a parallel execution flow.

The code above should loop for at most 0.01 seconds - assuming the destroy succeeds .

The idea is to block until we are sure the object no longer exists.

Unfortunately nwn doesn't have any real Thread.Sleep methods.

Mainly due to its single threaded nature: eg: if you pause the mainloop, then you would be unable to unpause IT from the mainloop, since it would be in effect frozen.

So for that reason- any pseudo waits would need to keep the script / mainloop thread moving. Hence a while loop.
               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Persistent merchants?
« Reply #6 on: April 26, 2015, 12:39:16 am »


               

I don't believe the delay commands work like interrupts. That would add a whole mess of complexity to scripting which is not there now.


 


It really is single threaded. What you posted TMIs just as I thought it would. 


 


Of course there are no thread sleep methods. There are no threads to sleep. I get the idea of your code, but it requires there is something that will execute outside of your loop to change the loop condition. But that's not the case here.



               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #7 on: April 27, 2015, 12:52:11 am »


               

Even the time does not change throughout a script.  For example, while (nTime == GetTimeMilliseconds()) would continue to produce the same value no longer how long it takes to TMI.



               
               

               
            

Baaleos

  • Administrator
  • Hero Member
  • *****
  • Posts: 1916
  • Karma: +0/-0
Persistent merchants?
« Reply #8 on: April 27, 2015, 08:00:16 am »


               Well that just depends on the resolution of what time your retrieving.

If I loop continuously and ask it to print out the time in miliseconds, it will print a lot of duplicate values, yes: but eventually it will print new values, as there is only so many instructions it can execute within a single millisecond. seconds are much more likely to induce a TMI, as more instructions can be squeezed into a second, vs a millisecond.


The TMI message itself isn't triggered because a script 'takes too long to exit' it's because it only has so much memory to allocate for instructions per script execution. It can also be induced if a script is called too often (AI scripts during battle can induce it)

So the question is: will

O == OBJECT_INVALID before that limit is met, or will O refuse to be destroyed before the TMI limit is met.

I keep forgetting that I do script with a higher TMI limit than vanilla NWN, so for me- the answer is yes- O typically destroys itself rather fast.


As far as the delay commands go:

I believe it's more like a scheduling system.

Everything is on a single thread- the MainLoop

But when a delay command is called, the method targeted is scheduled to appear on the MainLoop at a time in the future.


Unfortunately delay commands do have the downside that the only way to block execution until they are done, is to do some sort of loop checking for a condition.  That looping Will of course freeze the server.


There is another way, which isn't traditional:

In a modified nwnx_dotnet I made, I am able to execute my own .net methods on brand new threads, adding a real dual/multi threaded nature to Nwn.

Combined with my own nwnDotNet project I made, which does MainLoop hooking, I get the ability to do realistic .sleep commands, without the risk of perpetual thread locking.


The only other way to block execution of a script , while waiting for a condition, is more complex to write on the forum, but involves using pseudo loops or breaking the functionality apart and executing using a switch/case type system.

If this has been done, do this, if that's been done, then do this. The case statement would the call itself via delay command , in order to reevaluate its place in the case statement.


The only problem with pseudoloops is that they do not maintain an easily perceived execution flow.

delayCommand(3.00.

Might seem like a predictable 3.00 delay before executing a function, but taking into consideration that nothing in nwscript (or any programming language) is instant, then we can see that the 3.00 is more like 3.00+?

This variable is more pronounced when looping rapidly (looping through module areas)

And even then- the delayCommand could potentially induce a TMI, in the same way that AI scripts can.

If a single script is called too rapidly within a short duration, then TMI can ensue...


Anyway geek rant over
               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Persistent merchants?
« Reply #9 on: April 27, 2015, 03:15:12 pm »


               

TMI is casued by reaching an instruction count limit. That is the number of instructions a script can execute before the engine kills it. It doesn't have anything to do with memory.  Also, I don't believe it matters how many times you call a script because each is its own execution and thus gets its own instruction count. AI scripts that throw TMIs do so because they have too many loops, or do too much within them.  (I find it hard to believe the developers would bother tracking scripts and maintaining the counts so they could implement a called-to-often check). 


 


As I understand it the mainloop is what's running the script. When done it can look at the next event. That may indeed be the command which was delayed. Having scripts be interrupted by other scripts would require a whole different programming model than what is currently used in nwscript.



               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #10 on: April 27, 2015, 04:12:25 pm »


               


Well that just depends on the resolution of what time your retrieving.

If I loop continuously and ask it to print out the time in miliseconds, it will print a lot of duplicate values, yes: but eventually it will print new values, as there is only so many instructions it can execute within a single millisecond. seconds are much more likely to induce a TMI, as more instructions can be squeezed into a second, vs a millisecond.

 




 


No, the time retrieved is from the time stamp of the script execution.  It does not update the time within the script.


               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #11 on: April 27, 2015, 04:25:11 pm »


               


 


As I understand it the mainloop is what's running the script. When done it can look at the next event. That may indeed be the command which was delayed. Having scripts be interrupted by other scripts would require a whole different programming model than what is currently used in nwscript.




 


Correct.  If you have two scripts delayed 15.0 seconds, the engine will perform them one at a time, even though the time it actually finishes the first script of the pair will be after the scheduled time slot for the second script's execution.  This causes lag (not much usually) as the game needs to catch up to real time.


               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #12 on: April 27, 2015, 04:31:39 pm »


               


  Also, I don't believe it matters how many times you call a script because each is its own execution and thus gets its own instruction count.




 


Executing a script without a delay is part of the main script and the executed script is fully resolved before proceeding further into the main script.  Same thing with SignalEvent() (which executes a creature's event script) and EffectDamage (the OnDamaged script).


               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Persistent merchants?
« Reply #13 on: April 27, 2015, 04:51:00 pm »


               


Executing a script without a delay is part of the main script and the executed script is fully resolved before proceeding further into the main script.  Same thing with SignalEvent() (which executes a creature's event script) and EffectDamage (the OnDamaged script).




I did not mean with ExectuteScript.  I just meant a running script.  Yes, without a delay Execute(ed)Scripts are part of the same count. I did not think Baaleos meant recursive calls the the same script via ExecuteScript. That is certainly another way to get the instruction count up. But in my experience it is the loops that really cause it. You only get, what, seven levels of scripts calling scripts before it just stops doing more and then you get odd/missing results.


 


Are you sure about SignalEvent though? I think that adds an event to the main loop's event queue and then gets run in its own context. But I have not explicitly tested for that. 


               
               

               
            

Legacy_WhiZard

  • Hero Member
  • *****
  • Posts: 2149
  • Karma: +0/-0
Persistent merchants?
« Reply #14 on: April 27, 2015, 04:57:56 pm »


               


I did not mean with ExectuteScript.  I just meant a running script.  Yes, without a delay Execute(ed)Scripts are part of the same count. I did not think Baaleos meant recursive calls the the same script via ExecuteScript. That is certainly another way to get the instruction count up. But in my experience it is the loops that really cause it. You only get, what, seven levels of scripts calling scripts before it just stops doing more and then you get odd/missing results.


 


Are you sure about SignalEvent though? I think that adds an event to the main loop's event queue and then gets run in its own context. But I have not explicitly tested for that. 




 


No, SignalEvent gets resolved within the script (as does the OnDamaged script if applying damage without a delay).


 


And the seven levels are easier to reach then most people think.  For instance if I used Cast spell: unique power with a damage application I would have:


1) cast spell script


2) module script for unique power


3) tag based script


4) creature's OnDamaged script