Aller au contenu

Photo

Persistent merchants?


  • Veuillez vous connecter pour répondre
21 réponses à ce sujet

#1
Surek

Surek
  • Members
  • 94 messages

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.



#2
Baaleos

Baaleos
  • Members
  • 1 330 messages

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.


  • Zwerkules et Grani aiment ceci

#3
WhiZard

WhiZard
  • Members
  • 1 204 messages
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).



#4
Baaleos

Baaleos
  • Members
  • 1 330 messages

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.



#5
meaglyn

meaglyn
  • Members
  • 809 messages

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.



#6
Baaleos

Baaleos
  • Members
  • 1 330 messages
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.

#7
meaglyn

meaglyn
  • Members
  • 809 messages

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.



#8
WhiZard

WhiZard
  • Members
  • 1 204 messages

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.



#9
Baaleos

Baaleos
  • Members
  • 1 330 messages
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

#10
meaglyn

meaglyn
  • Members
  • 809 messages

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.



#11
WhiZard

WhiZard
  • Members
  • 1 204 messages

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.



#12
WhiZard

WhiZard
  • Members
  • 1 204 messages

 

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.



#13
WhiZard

WhiZard
  • Members
  • 1 204 messages

  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).



#14
meaglyn

meaglyn
  • Members
  • 809 messages

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. 



#15
WhiZard

WhiZard
  • Members
  • 1 204 messages

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



#16
Baaleos

Baaleos
  • Members
  • 1 330 messages

The reason I mentioned Memory - is because i think I read years ago on the old lexicon or old forums something around the reasoning of why AI Scripts are able to generate TMI, even if they themselves are small.

 

It was something to do with a 'memory' limit (perhaps it wasn't memory) : being hit across the different entities that were calling the same script.

 

Eg: 500 NPC's all calling the same script: I experienced this when I had 30 npc's who suddenly tried to summon minions at the same time.

Each one would be its own individual ExecuteScript call - in essence, but it was something like too many instructions in too short a time for that one script.



#17
meaglyn

meaglyn
  • Members
  • 809 messages

@WhiZard: Wow, okay. That makes my head hurt... :blink:  I'll have to poke at that.  

As to the seven levels, yes I've hit that. It sucks because there is no error. I'd love it if there was a TML (too many levels) error instead of the undefined results caused by not actually executing the script you get now :)

@Baaleos: I bet those AI scripts have lots of GetNearestObject/Creature loops (or other loops that depend on the number of nearby objects/creatures, number of effects on the objects etc) in them. It does not take a large script to get to TMI, just a lot of objects to loop over.

 



#18
meaglyn

meaglyn
  • Members
  • 809 messages

To completely finish hijacking Surek's thread...

 

@WhiZard

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

 

 

This is not born out by my testing for general SignalEvent calls. I did not have a lot of time so did not explicitly test the EffectDamage path.

I did two tests. The first was simply having a lever onused event something like this :

void main() {
    SendMessageToPC(GetFirstPC(), "Starting signal event");
    SignalEvent(GetObjectByTag("TESTME1"), EventUserDefined(501));
    SendMessageToPC(GetFirstPC(), "Done with signal event.");
} 

The userdefined event handler on TESTME1 just prints a message saying it got an event.

 

The results were:

      Starting signal event

      Done with signal event

      TESTME1 got event

 

If it worked the way you described the "TESTME1 got event" line would have been in the middle.

 

Test 2 was similar but I had TESTME1 send the event to TESTME2 when it got the first event. TESTME2 then sent it right back, creating a loop of two objects sending each other events as fast as possible, no delays. If this was all happening it the context of the original script this loop would have stopped with a TMI. Instead the CPU was pegged and the game completely stopped until I killed nwmain.

 

Interestingly in this case none of the messages printed. It must have tied up the server before the client side could do anything. This was single player so it was all the same process.



#19
WhiZard

WhiZard
  • Members
  • 1 204 messages

To completely finish hijacking Surek's thread...

 

@WhiZard

 

This is not born out by my testing for general SignalEvent calls. I did not have a lot of time so did not explicitly test the EffectDamage path.

I did two tests. The first was simply having a lever onused event something like this :

void main() {
    SendMessageToPC(GetFirstPC(), "Starting signal event");
    SignalEvent(GetObjectByTag("TESTME1"), EventUserDefined(501));
    SendMessageToPC(GetFirstPC(), "Done with signal event.");
} 

The userdefined event handler on TESTME1 just prints a message saying it got an event.

 

The results were:

      Starting signal event

      Done with signal event

      TESTME1 got event

 

If it worked the way you described the "TESTME1 got event" line would have been in the middle.

 

Test 2 was similar but I had TESTME1 send the event to TESTME2 when it got the first event. TESTME2 then sent it right back, creating a loop of two objects sending each other events as fast as possible, no delays. If this was all happening it the context of the original script this loop would have stopped with a TMI. Instead the CPU was pegged and the game completely stopped until I killed nwmain.

 

Interestingly in this case none of the messages printed. It must have tied up the server before the client side could do anything. This was single player so it was all the same process.

 

Thanks.  I modified the fireball spell to get rid of the DelayCommands() and then had "SPELL" returned for the OnSpellCastAt event and "DAMAGE" returned for the OnDamaged event.  I marked "Fireballstart" and "Fireballend" for each creature in the loop and got the following return.

 

Fireballstart

DAMAGE

Fireballend

Fireballstart

DAMAGE

Fireballend

SPELL

SPELL

 

So it looks like Signal event does delay to after script execution, while the applying EffectDamage() does not.



#20
meaglyn

meaglyn
  • Members
  • 809 messages

Cool! That hurts less :)

 

Thanks. Now I don't have to do that one.  I'm not too surprised they are different. One is an explicit raising of an event while the other is applying an effect which has a side effect of calling one of the creatures scripts. That is, I don't think of ApplyEffect(EffectDamage()) as synonymous with SignalEvent(EventOnDamaged()) - if there was such a thing.  Good to know... OnDamaged script is not the place to do heavy lifting.



#21
Valgav

Valgav
  • Members
  • 64 messages

I've read whole topic and i have question?

 

I need to run few scripts on some intervals for example every time when module change hour. 

 

Is it better to use signal event or ExecuteScript +/- delay?

 

My scripts are quite big and have lot stuff to do.



#22
meaglyn

meaglyn
  • Members
  • 809 messages

As long as you do the ExecuteScript in a delay (even 0.0) it probably does not matter.  Both cases will give you a full instruction limit script execution environment. Personally, I like ExecuteScript for this because it keeps things compartmentalized without needing to be in include files or in a really big userdefined handler scripts. If I signalled an event on the module for hours passing I'd probably end up having the userdefined event handler

ExecuteScript the work anyway.

 

Also if you had several unrelated things to do every hour you could spread them out a bit with different delays so they don't all fall right on the heartbeat.