Modifié par M. Rieder, 12 décembre 2011 - 03:33 .
trouble with ga_take_gold (Resolved)
#1
Posté 11 décembre 2011 - 08:56
#2
Posté 11 décembre 2011 - 10:44
#3
Posté 12 décembre 2011 - 12:04
The default gold scripts either create gold from nothing to give to the player (ga_give_gold), or destroy some of the PC's gold so that it's gone forever (ga_take_gold).
In this house, we obey the laws of thermodynamics!
#4
Posté 12 décembre 2011 - 02:01
#5
Posté 12 décembre 2011 - 03:33
////////////////////////////////////////////
//takes gold from target
//sTarget = tag of object, can use parameter const.
//nGold = how much gold to take.
#include "ginc_param_const"
void main(string sTarget, int nGold)
{
object oTarget = GetTarget(sTarget);
TakeGoldFromCreature(nGold,oTarget,TRUE,TRUE);
}
#6
Posté 12 décembre 2011 - 09:15
// ga_transfer_items
/*
This takes items from one creature and gives them to another creature
sItemTag = This is the string name of the item's tag. For gold, use GOLD
nQuantity = The number of items to destroy. -1 is all of the items of that tag.
sTarget = Tag of creature to give it to. If blank, use PC speaker
sGiver = Tag of creature to take it from. If blank, use PC speaker
*/
#include "nw_i0_plot"
void TransferItems(object oFrom, object oTo, string sItem,int nNumItems)
{
int nCount = 0;
string sResRef;
object oItem = GetFirstItemInInventory(oFrom);
while (GetIsObjectValid(oItem) == TRUE && nCount < nNumItems)
{
if (GetTag(oItem) == sItem)
{
sResRef = GetResRef(oItem);
int nRemainingToDestroy = nNumItems - nCount;
int nStackSize = GetItemStackSize(oItem);
if(nStackSize <= nRemainingToDestroy)
{
DestroyObject(oItem,0.1f);
nCount += nStackSize;
}
else
{
int nNewStackSize = nStackSize - nRemainingToDestroy;
SetItemStackSize(oItem, nNewStackSize);
break;
}
}
oItem = GetNextItemInInventory(oFrom);
}
CreateItemOnObject(sResRef, oTo, nNumItems);
return;
}
void main(string sItemTag,int nQuantity, string sTarget, string sGiver)
{
int nTotalItem;
object oTarget = GetPCSpeaker();
if (sTarget != "")
oTarget = GetObjectByTag(sTarget);
object oFrom = GetPCSpeaker();
if (sGiver != "")
oFrom = GetObjectByTag(sGiver);
if (sItemTag == "GOLD")
{
TakeGoldFromCreature(nQuantity, oFrom, TRUE, TRUE);
GiveGoldToCreature(oTarget, nQuantity, TRUE);
return;
}
object oItem; // Items in inventory
if ( nQuantity < 0 ) // Destroy all instances of the item
{
nTotalItem = GetNumItems( oFrom,sItemTag );
TransferItems( oFrom, oTarget, sItemTag,nTotalItem );
}
else
{
TransferItems( oFrom, oTarget, sItemTag,nQuantity );
}
}
#7
Posté 12 décembre 2011 - 10:08
Uses the GetTarget convo function ( basically same as what comes with vanilla game and allows use of "$PC_SPEAKER" type parameters which are listed in the constants at the top. ) When no parameters entered it should give item from the person speaking to the player who started the conversation.
Caps gold to amount held by giver.
Uses the actual items held by the player, does not recreate them from the resref but moves them from one inventory to another, thus preserving charges. Will take the correct number of items even if it's split up in the inventory, and should split properly.
Reduces loops thru inventory to just one - some players have large inventories.
// ga_transfer_items
/*
This takes items from one creature and gives them to another creature
sItemTag = This is the string name of the item's tag. For gold, use GOLD
nQuantity = The number of items to destroy. -1 is all of the items of that tag.
sTarget = Tag of creature to give it to. If blank, use PC speaker
sGiver = Tag of creature to take it from. If blank, use PC speaker
*/
//#include "nw_i0_plot"
#include "_CSLCore_Messages"
#include "_CSLCore_Items"
/*
// for reference only as they are in the CSL Library
const string CSLTARGET_KEY = "$";
const string CSLTARGET_OBJECT_SELF = "$OBJECT_SELF"; // OBJECT_SELF
const string CSLTARGET_OWNER = "$OWNER"; // OBJECT_SELF (conversation owner)
const string CSLTARGET_OWNED_CHAR = "$OWNED_CHAR"; // GetOwnedCharacter
const string CSLTARGET_PC = "$PC"; // PCSpeaker
const string CSLTARGET_PC_LEADER = "$PC_LEADER"; // FactionLeader (of first PC)
const string CSLTARGET_PC_NEAREST = "$PC_NEAREST"; // NearestPC (owned and alive)
const string CSLTARGET_PC_SPEAKER = "$PC_SPEAKER"; // PCSpeaker
const string CSLTARGET_MODULE = "$MODULE";
const string CSLTARGET_LAST_SPEAKER = "$LASTSPEAKER";
const string CSLPARAM_COMMONER = "$COMMONER";
const string CSLPARAM_DEFENDER = "$DEFENDER";
const string CSLPARAM_HOSTILE = "$HOSTILE";
const string CSLPARAM_MERCHANT = "$MERCHANT";
object CSLGetTarget(string sTarget, string sDefault=CSLTARGET_OWNER)
{
object oTarget = OBJECT_INVALID;
// If sTarget is blank, use sDefault
if ("" == sTarget || "0" == sTarget) sTarget = sDefault;
// Check if sTarget is a special identifier
if ( GetStringLeft(sTarget, 1) == CSLTARGET_KEY )
{
string sIdentifier = sTarget;
sIdentifier = GetStringUpperCase(sIdentifier);
if (CSLTARGET_OWNER == sIdentifier) oTarget = OBJECT_SELF;
else if (CSLTARGET_OBJECT_SELF == sIdentifier) oTarget = OBJECT_SELF;
else if (CSLTARGET_OWNED_CHAR == sIdentifier) oTarget = GetOwnedCharacter(OBJECT_SELF);
else if (CSLTARGET_PC == sIdentifier) oTarget = GetPCSpeaker();
else if (CSLTARGET_PC_LEADER == sIdentifier) oTarget = GetFactionLeader(GetFirstPC());
else if (CSLTARGET_PC_NEAREST == sIdentifier) oTarget = CSLGetNearestPC();
else if (CSLTARGET_PC_SPEAKER == sIdentifier) oTarget = GetPCSpeaker();
else if (CSLTARGET_MODULE == sIdentifier) oTarget = GetModule();
else if (CSLTARGET_LAST_SPEAKER == sIdentifier) oTarget = GetLastSpeaker();
else
{
//PrettyError("GetTarget() -- " + sIdentifier + " not recognized as special identifier!");
}
}
else
{
oTarget = GetNearestObjectByTag(sTarget); // Search area
//EPF 4/13/06 -- get nearest misses if the owner is the object we're looking for
// so check and see if the target is OBJECT_SELF. I'm putting this after the GetNearest()
// call since string compares are expensive, but before the GetObjectByTag() call, since
// that's liable to return the wrong instance. We can move this to before the GetNearest() call
// if this becomes a problem.
if(!GetIsObjectValid(oTarget))
{
if(sTarget == GetTag(OBJECT_SELF))
{
oTarget = OBJECT_SELF;
}
}
// If not found
if (GetIsObjectValid(oTarget) == FALSE)
{
oTarget = GetObjectByTag(sTarget); // Search module
}
}
// If not found
//if (GetIsObjectValid(oTarget) == FALSE)
//{
// //PrettyDebug("GetTarget() -- Could not find target with tag: " + sTarget);
//}
return (oTarget);
}
void CSLSplitAndTransferItem( object oItem, object oTarget, int iNumberItems )
{
int nStackSize = GetNumStackedItems(oItem);
int iNumberToGive = CSLGetMin(iNumberItems,nStackSize);
int iNumberToKeep = CSLGetMax(0,nStackSize-iNumberToGive);
if ( iNumberToKeep > 0 && iNumberToGive > 0 )
{
SetItemStackSize( oItem,iNumberToGive, FALSE);
CopyItem(oItem, oTarget, TRUE);
SetItemStackSize( oItem,nStackSize, FALSE);
SetItemStackSize( oItem,iNumberToKeep, TRUE); // now notify the target
}
else if ( iNumberToGive > 0 )
{
ActionGiveItem(oItem,oTarget);
}
}
void CSLGiveNumItems(object oTarget, string sItem,int nNumItems, int bAdjustStacks = FALSE, object oGiver = OBJECT_SELF )
{
int iRemainingItems = nNumItems;
int nStackSize;
object oItem = GetFirstItemInInventory(oTarget);
if ( bAdjustStacks && nNumItems != -1 ) // broke down logic into seperate so not re-evaluating this as much - nNumItems if unlimited will not deal with stacks
{
while (GetIsObjectValid(oItem) == TRUE && iRemainingItems > 0 )
{
if (GetTag(oItem) == sItem)
{
nStackSize = GetNumStackedItems(oItem);
if ( nStackSize > iRemainingItems )
{
//SetItemStackSize( oItem, nStackSize-iRemainingItems, TRUE); // with feedback for now
CSLSplitAndTransferItem(oItem,oTarget,iRemainingItems);
}
else
{
ActionGiveItem(oItem, oTarget);
}
iRemainingItems -= nStackSize;
}
oItem = GetNextItemInInventory(oTarget);
}
}
else
{
while (GetIsObjectValid(oItem) == TRUE && ( nNumItems == -1 || iRemainingItems > 0 ) )
{
if (GetTag(oItem) == sItem)
{
ActionGiveItem(oItem,oTarget);
iRemainingItems--;
}
oItem = GetNextItemInInventory(oTarget);
}
}
return;
}
*/
void main(string sItemTag,int nQuantity, string sTarget, string sGiver) // note not defaulting parameters as that throws exception in new compiler
{
if ( sGiver == "" )
{
sGiver = CSLTARGET_PC_SPEAKER; // GetPCSpeaker();
}
/*
// removed this, this should be taken care of via the CSLGetTarget function, if both blank will give from the speaker to the person being spoken to by default
if ( sTarget == "" )
{
if ( sGiver == CSLTARGET_PC_SPEAKER )
{
sTarget = CSLTARGET_OWNER; // OBJECT_SELF
}
else
{
sTarget = CSLTARGET_PC_SPEAKER; // GetPCSpeaker();
}
}
*/
object oTarget = CSLGetTarget(sTarget);
object oFrom = CSLGetTarget(sGiver);
// short circuit if tag used is "gold", case insensitive and capped to amount of gold held by giver
if ( GetStringUpperCase(sItemTag) == "GOLD")
{
int nGold = GetGold( oFrom );
if ( nGold < nQuantity )
{
nQuantity = nGold;
}
TakeGoldFromCreature(nQuantity, oFrom, TRUE, TRUE);
GiveGoldToCreature(oTarget, nQuantity, TRUE);
return;
}
CSLGiveNumItems(oTarget, sItemTag ,nQuantity, TRUE, oFrom );
return;
}
#8
Posté 12 décembre 2011 - 10:53
I tend not to build many error traps into my scripts, since I know how to set up the variables in the conversation to avoid them. Although they're a good idea if you're making a general script for lots of people to use (many of whom may not be scripters themselves).
#9
Posté 12 décembre 2011 - 11:08
Pretty sure what i did will work with what you set up, and it gives you more functionality as a general purpose tool, which is what i see convo scripts as really being, small commands usable by conversation scripters who i assume are not really programmers. And what you had was pretty cool as an idea, i just thought if it's out there for general use it should have a more solid foundation.
#10
Posté 13 décembre 2011 - 10:22
#11
Posté 13 décembre 2011 - 12:55
I once left a simple check for "if (i>0)" out thinking - rather not thinking - what's the harm if a 0 goes through. Several months down the road it crashed my game and spent weeks banging head vs. wall trying to figure out what was wrong.
etc, like having rummaged through the OC for the past coupla years i'm sure the bugs are caused by ... sloppy scripting. A tight script is a HAPPY script!
( lol ) - but if a small one works, it works; personally i'm gonna snag the illustrious code dealt us above
#12
Posté 13 décembre 2011 - 05:51
I suspect there is a campaign setting that I do not know about which was interfering with things.
@Pain:
You should make bumper stickers. "You should always script defensively..." What a great catch phrase. True also. If you would be so kind as to post some common "bug catchers" that you use, it would be very helpful.
#13
Posté 13 décembre 2011 - 05:53
kevL wrote...
( lol ) - but if a small one works, it works; personally i'm gonna snag the illustrious code dealt us above
If you use the code I wrote, keep in mind that the gold gets destroyed. It does not get transferred to anything. You would have to change one of the "TRUE" parameters to false, but I can't remember which one off the top of my head. It's in the toolet's script assist, though.
#14
Posté 13 décembre 2011 - 06:39
- if a value could be anything else than what you expect, do a check
eg.
if (iGold < 1) { return; } at the top.eg2.
if (!GetIsObjectValid(oTarget)) { /???/ }
- an
if ()statement always assumes a "what if?" statement (whether explicit or not, it's there)
It becomes habit, a way of seeing after a while; when coding an IF statement, follow it up at least implicitly with an ELSE
the fact the virtual machine is buggy isn't an excuse: it's a paranoia. Murry x-mas!!
#15
Posté 13 décembre 2011 - 06:41
M. Rieder wrote...
The funny thing is that I have spoken to other modders who have had no problem with the stock version of the script. I really don't understand why. I do confess that I didn't spend much time looking into it when I found that my custom script worked. I checked to see if the polymorph was the cause and it was not.
I suspect there is a campaign setting that I do not know about which was interfering with things.
@Pain:
You should make bumper stickers. "You should always script defensively..." What a great catch phrase. True also. If you would be so kind as to post some common "bug catchers" that you use, it would be very helpful.
Bugs are only noticed by a few users. Saying you had no issues, does not mean there aren't any, it's just you are not using said code often enough or in enough situations to notice the error. Some code just won't work in multiplayer, or outside the module it was written for, and that is often a shame because it's often ideas which would be very useful to the community. ( good example is the overland map code, great ideas, works perfect in SOZ, but good luck using it in your own modules, much less multiplayer without rolling up your sleeves and almost redoing it ).
What i basically do is reduce the amount of overall code. If you have, what i call an implementation script, well it should not include all 5000 files in the stock library that comes with the game. If i see an error of any sort, i spend time to review the entire codebase and apply the "fix" globally ensuring it just does not happen again.
I try to keep what i call "implementation" scripts very light, with most of the code in libraries which are shared. This means that every time i create a new script that uses a given function I actually am re-testing old un-related code in that function and quite often i end up fixing even my old code. While my code is described as "too much information", it took me about 10 minutes to implement similar functionality - most of that was already written and it's actually only about 60 lines.
Keep the code clean, indented, and be extremely picky about formatting. I remember using a open source PDF library for a project at work, and just in reformatting it so it's easier for me to read, i fixed about a dozen now obvious errors. Feel free to change how it looks so it's easier to read for you and add your own comments.
I do not use stock libraries. Errors in the stock libraries are very hard to find, with hundreds of files involved just from a single include, and frankly I spent more time chasing down bugs which crashed the toolset mysteriously instead of doing actual code. And i am talking about really amateur errors. If you need such code, make your own library, copy just what you need into it, review that line by line, and use that instead.
Use lots of comments, it's always better to have too much information on how things work, than not enough, especially when you look at the code a year after you wrote it. Comparing the documentation to the code, allows others to notice errors in logic on your part, and without those it's guess work to know how it should actually work. Keep all notes on how things work in the code, i use for example javadoc formating and then create documentation with doxygen which includes graphs showing includes and the like.
Use @bug and @todo javadoc comments if you cannot deal with an issue right away, this ensures you get a list at a later point and you won't forget about it. This assumes you use doxygen, but even search will find these for you at some point. ( and if others use your code, it is a lot easier to re-find the issue if there is a comment about it right there )
Assume end users are evil and out to get you, and be very pessimistic. Most often the issues come up when people are using code in ways you never planned on or intended, and if your code is strong enough it will end up working despite this. It is a lot easier to prevent errors than it is to find them after the code is completed. Only by being pessimistic as a coder, will your end users will have reason to be optimistic that they won't have issues.
I think of each function as a contract, it should work as advertised by it's name, parameters and to a lesser degree it's documentation. Ideally each function should check for validity, making sure objects are valid, and abort or provide a meaningful debug message if those parameters don't make sense.
Refactor, Refactor, Refactor. Treat your own code like someone else wrote it, and rewrite it to improve it periodically. The act of redoing it will make you notice things you did not think of the first time thru. If others have similar code, review theirs, and refactor yours to merge any improvements, bug fixes, ideas they have.
Here is the actual finished code, i tweaked a few things, got it into proper javadoc format ( which should make it much easier to read for non scripters ), made gold take all a targets gold if -1 is used, flop flopped the target and giver defaults after re-reading the $PC_SPEAKER documentation. ( assuming by default the player should get something from the NPC ).
Also need to review $PC_LEADER as that won't work in MP at all, and a library should work by default regardless of setting without forcing end users to change how they do things. ( should be the faction leader of $PC_SPEAKER i think but need to be careful it does not change how SP modules work in any way. )
/**
* Transfers items from Giver to Target
*
* sGiver and sTarget can use standard Conversation Parameters
* $OBJECT_SELF - OBJECT_SELF or NPC you are having conversation with (conversation owner)
* $OWNER - NPC you are having conversation with (conversation owner)
* $OWNED_CHAR - GetOwnedCharacter ( actually gets teh
* $PC - PCSpeaker ( Player who started the conversation ) ( "GetPCSpeaker()" )
* $PC_LEADER - FactionLeader ( of first PC which only works in Single player )
* $PC_NEAREST - NearestPC (owned and alive)
* $PC_SPEAKER - PCSpeaker ( "GetPCSpeaker()" )
* $MODULE - The module itself
* $LASTSPEAKER - The last speaker ( "GetLastSpeaker()" )
*
* or the creatures tag, which gets the nearest creature with that tag
*
* @author DannJ original idea and script, refactored by pain
* @param sItemTag This is the string name of the item's tag. For gold, use "GOLD".
* @param nQuantity The number of items to destroy. -1 is all of the items of that tag.
* @param sTarget Tag of creature to give it to. If blank use PC speaker ( the player who started the conversation usually ).
* @param sGiver Tag of creature to take it from. If blank use NPC being spoken to ( OBJECT_SELF )
* @todo add logging of invalid objects, in a way they can only be seen in development
*/
#include "_CSLCore_Messages"
#include "_CSLCore_Items"
void main(string sItemTag,int nQuantity, string sTarget, string sGiver) // note not defaulting parameters as that throws exception in new compiler
{
if ( sTarget == "" )
{
sTarget = CSLTARGET_PC_SPEAKER; // GetPCSpeaker();
}
if ( nQuantity < -1 )
{
// log error here, or add new functionality, cannot transfer if less than -1
// log should alert scripter
return;
}
if ( sItemTag == "" )
{
// log error here, or add new functionality, cannot transfer if no item tag
// log should alert scripter
return;
}
object oTarget = CSLGetTarget(sTarget);
object oFrom = CSLGetTarget(sGiver);
if ( !GetIsObjectValid(oTarget) || !GetIsObjectValid(oTarget) )
{
// log error here, or add new functionality, cannot transfer if both don't exist
// log should alert scripter
return;
}
// short circuit if tag used is "gold", case insensitive and capped to amount of gold held by giver
if ( GetStringUpperCase(sItemTag) == "GOLD")
{
int nGold = GetGold( oFrom );
if ( nQuantity == -1 ) // -1 indicates take everything
{
nQuantity = nGold;
}
if ( nGold < nQuantity )
{
nQuantity = nGold;
}
TakeGoldFromCreature(nQuantity, oFrom, TRUE, TRUE);
GiveGoldToCreature(oTarget, nQuantity, TRUE);
return;
}
CSLGiveNumItems(oTarget, sItemTag ,nQuantity, TRUE, oFrom );
return;
}
Modifié par painofdungeoneternal, 13 décembre 2011 - 06:58 .
#16
Posté 13 décembre 2011 - 06:55
kevL wrote...
two notes on DS (defensive scripting) :
- if a value could be anything else than what you expect, do a check
eg.if (iGold < 1) { return; }at the top.
eg2.if (!GetIsObjectValid(oTarget)) {/???/}
- anif ()statement always assumes a "what if?" statement (whether explicit or not, it's there)
It becomes habit, a way of seeing after a while; when coding an IF statement, follow it up at least implicitly with an ELSE
the fact the virtual machine is buggy isn't an excuse: it's a paranoia. Murry x-mas!!
Yes need to add less than -1 as a check as well to the code being discussed. Good catch on that.
#17
Posté 13 décembre 2011 - 07:05
#18
Posté 13 décembre 2011 - 11:03
M. Rieder wrote...
The funny thing is that I have spoken to other modders who have had no problem with the stock version of the script. I really don't understand why. I do confess that I didn't spend much time looking into it when I found that my custom script worked. I checked to see if the polymorph was the cause and it was not.
I suspect there is a campaign setting that I do not know about which was interfering with things.
The stock version of the script is fine. ga_take_gold takes gold away from the party. ga_give_gold gives gold to the party. You just used the wrong script.
Regards
#19
Posté 14 décembre 2011 - 12:09
#20
Posté 14 décembre 2011 - 12:35
M. Rieder wrote...
No, I made a typo the when I first wrote the thread. When I originally wrote the thread, I accidentally wrote ga_give_gold when I meant to write ga_take_gold. I have since corrected it. I have repeatedly tested ga_take_gold in my campaign and for some reason that I do not understand, the gold is taken from the PC and then immediatly returned. When I re-wrote the script with slightly different parameters than the stock, it started to work.
All of my innkeepers in my campaigns are happy to take gold from the party and not a one has ever returned it. I've always used the standard script.
#21
Posté 14 décembre 2011 - 01:08
#22
Posté 14 décembre 2011 - 01:13
Like the pc being a TROLL? Those slobbering, ugly things are pretty darn obvious!M. Rieder wrote...
Yeah. PJ said he's used it successfully too. I think that I have used it successfully in the past as well. I'm sure it is something silly and obvious, but I just couldn't find it.
#23
Posté 14 décembre 2011 - 01:58
Good point.





Retour en haut






