Aller au contenu

Photo

Adapting a Climbing Script to Objects


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

#1
Sabranic

Sabranic
  • Members
  • 306 messages

I am in the process of trying to modify Markshire's Climbing Code to require the use of rope, and give some bonuses based on class.  The intent is to bring a bit more of the skills available to rogues and bards into the game for traversing rough terrain, while also adding some fun dead falls which players will need to carry rope to deal with.   I plan on adding pit traps that will require climbing out of, as well as sheer surfaces which character can climb to reach otherwise inaccessible locations/rewards/quests.

 

So far I have created the following items:

mca_rope1.UTI - Standard rope, anyone can use, high chance of falling.  (Stacks to 10).

mca_rope2.UTI - Professional rope, only rogues and bards can use, low chance of falling. (Stacks to 10).

mca_rope3.UTI - Enchanted rope, only rogues and bards can use, very low chance of falling. (Unlimited Uses).

 

What I don't understand is how to make the first two increment down each use.  I have been trying to re-purpose the following code to this task, but I am having troubles:

	if(iFound==TRUE){
		//The rope item is removed and the PC is allowed to climb.
			int iStackSize = GetItemStackSize(oItem);
			if(iStackSize==1){
				DestroyObject(oItem);
			}
			else{
				SetItemStackSize(oItem, iStackSize-1);
			}

Here is the existing code, which I altered as follows:

- Added a climbing bonus for the rope 1.

- Added a climbing bonus for the rope 2.

- Added a climbing bonus for the rope 3.

- Started trying and failing to add a bonus for the rogue & bard classes.

//  Markshire Climbing System - By Thrym of Markshire  
//  Created: 3/17/07  
// Modofied by Sabranic: 5/30/16                       

// ***** FUNCTION DECLARATIONS ***** 

// Determine if the PC can climb
// Fails if oClimber has anything in their right hand
// or are fully overloaded with encumbrance.
int ms_ClimbCheck(object oClimber);

// Returns the penalty (if any for oClimber)
// Heavey Encumbrance Penalty = +3
// Armor Check Penalty = +2/+4/+7 dependent
// Stealth Penalty = +5
int ms_CalcClimbPenalty(object oClimber);

// Returns the bonus (if any) for oClimber
// Climbing Gear Equipped Bonus = varies
// Climbing Magic Item (on person) Bonus = varies
// Halfling Race Bonus = +2
// Size Large or Greater Bonus = +2
int ms_CalcClimbBonus(object oClimber);

// Returns Success of an Ability
// Includes minor failure (5 or less under DC)
// and critical failure
// (termed as critical but only to differentiate between failure types)
int ms_GetAbilityCheckSuccessful(object oTarget, int iAbility, int iDC);

// Handle the move to the new location.
void ms_ClimbToDestination(object oClimber);

// Cause oClimber to animate falling down.
void ms_FallAnimation(object oClimber);


// ***** MAIN FUNCTION ***** //

void main ()
{
     object oPC = GetLastUsedBy();

     /*
     SendMessageToPC(oPC, "You have clicked on " + GetTag(OBJECT_SELF));
     int iDebugDC  = GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC");
     int iDebugHT  = GetLocalInt(OBJECT_SELF, "CLIMB_HEIGHT");
     int iDebugDIR = GetLocalInt(OBJECT_SELF, "CLIMB_DIRECTION");
     SendMessageToPC(oPC, "It's variables are...");
     SendMessageToPC(oPC, "Climb DC: " + IntToString(iDebugDC));
     SendMessageToPC(oPC, "Climb Height: " + IntToString(iDebugHT));
     SendMessageToPC(oPC, "Climb Direction: " + IntToString(iDebugDIR));     */

     if (GetLocalString(OBJECT_SELF, "CLIMB_DESTINATION") == "")
     {
          SendMessageToPC(oPC, "ERROR: Please inform the admins that this climb point has no destination. "
                                    + GetTag(OBJECT_SELF) + " in " + GetName(GetArea(OBJECT_SELF)));
          return;
     }

     // If the user is a DM just send em.
     if (GetIsDM(oPC) == TRUE || GetIsDMPossessed(oPC) == TRUE)
     {
          ms_ClimbToDestination(oPC);
          return;
     }

     // If they fail the check stop.
     if (ms_ClimbCheck(oPC) == FALSE) return;

     // Now we concern ourselves with variables
     int iDC = ms_CalcClimbPenalty(oPC) + GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC") - ms_CalcClimbBonus(oPC);
     int iHeight = GetLocalInt(OBJECT_SELF, "CLIMB_HEIGHT");
     int iDirection = GetLocalInt(OBJECT_SELF, "CLIMB_DIRECTION");

     int iDamage;
     int iAbility = ABILITY_STRENGTH;

     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_CHEST, oPC)) == "arm_climbinggear") iAbility = ABILITY_DEXTERITY;


     // Check their success.
     switch (ms_GetAbilityCheckSuccessful(oPC, iAbility, iDC))
     {
          case 0: // Critical Failure
               iHeight = iHeight > 20 ? 20 : iHeight;
               iDamage = d6(iHeight > 1 ? iHeight : 1);
               if (iDirection == 0) DelayCommand(1.5, ms_ClimbToDestination(oPC));
               DelayCommand(2.0, ms_FallAnimation(oPC));
               DelayCommand(1.5, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectDamage(iDamage), oPC));
               SetActionMode(oPC, ACTION_MODE_STEALTH, FALSE);
               break;

          case 1:  // Minor Failure
               iHeight = iHeight/2 > 20 ? 20 : iHeight/2;
               iDamage = d6(iHeight/2 > 1 ? iHeight/2 : 1);
               if (iDirection == 0) DelayCommand(1.5, ms_ClimbToDestination(oPC));
               DelayCommand(2.0, ms_FallAnimation(oPC));
               DelayCommand(1.5, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectDamage(iDamage), oPC));
               SetActionMode(oPC, ACTION_MODE_STEALTH, FALSE);
               break;

          case 2: // Success
               DelayCommand(1.5, ms_ClimbToDestination(oPC));
               break;
     }
}


// ***** FUNCTION ***** //

// Returns the penalty (if any for oClimber)
// Heavey Encumbrance Penalty = +3
// Armor Check Penalty = +2/+4/+7 dependent
// Stealth Penalty = +5
int ms_CalcClimbPenalty(object oClimber)
{
     object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oClimber);
     object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oClimber);

     int iEncumbrance = GetEncumbranceState(oClimber);

     int iPenalty = 0;

     if (iEncumbrance == ENCUMBRANCE_STATE_HEAVY) iPenalty = iPenalty + 3;

     switch (GetArmorRank(oArmor))
     {
          case ARMOR_RANK_LIGHT:  iPenalty = iPenalty + 2; break;
          case ARMOR_RANK_MEDIUM: iPenalty = iPenalty + 4; break;
          case ARMOR_RANK_HEAVY:  iPenalty = iPenalty + 7; break;
     }

     if (GetStealthMode(oClimber) == STEALTH_MODE_ACTIVATED) iPenalty = iPenalty + 5;

     return iPenalty;
}

// Returns the bonus (if any) for oClimber
// Climbing Gear Equipped Bonus = varies
// Climbing Magic Item (on person) Bonus = varies
// Halfling Race Bonus = +2
// Size Large or Greater Bonus = +2
int ms_CalcClimbBonus(object oClimber)
{
     object oItem;
     int iBonus = 0;

     // Check for items that are equipped only.

     // Silk Climbing Rope - Sabraic Addition 5/30/16
     if (GetResRef(GetFirstItemInInventory(oClimber)) == "mca_rope2") iBonus = iBonus + 10;

     // Rope of Climbing - Sabraic Addition 5/30/16
     if (GetResRef(GetFirstItemInInventory(oClimber)) == "mca_rope3") iBonus = iBonus + 15;
	 	 
	// Climbing Gloves
     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_ARMS, oClimber)) == "glv_climbinggloves") iBonus = iBonus + 5; 
	 
     // Ring of Feather Falling (Either Hand but not both)
     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_LEFTRING, oClimber)) == "rin_ringoffeatherfalling" ||
          GetResRef(GetItemInSlot(INVENTORY_SLOT_RIGHTRING, oClimber)) == "rin_ringoffeatherfalling")
          {iBonus = iBonus + 5;}

     // Check the PC for each of the possible climbing magic item resrefs.

     oItem = GetFirstItemInInventory(oClimber);
     string sResRef = GetResRef(oItem);

     while (oItem != OBJECT_INVALID)
     {
          sResRef = GetResRef(oItem);

          // if (sResRef == "resref_of_climbing_item") iBonus = iBonus + 1;

          oItem = GetNextItemInInventory(oClimber);
     }

     if (GetRacialType(oClimber) == RACIAL_TYPE_HALFLING) iBonus = iBonus + 2;
	 
     if (GetCreatureSize(oClimber) >= CREATURE_SIZE_LARGE) iBonus = iBonus + 2;
	
        // Check classes Bard and Thief Commented out for now, not working Sabranic 5/30/16
	// if (class_level(oClimber) >= 8) iBonus = iBonus + 5;
	// if (class_level(oClimber) >= 1) iBonus = iBonus + 5;
	 
     return iBonus;

}

// Returns the penalty (if any for oClimber)
// Based on Armor, Encumbrance, and type of gear in hand
int ms_ClimbCheck(object oClimber)
{
     object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oClimber);
     object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oClimber);
     object oPrimary = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oClimber);

     int iEncumbrance = GetEncumbranceState(oClimber);
     int iTwoHanded;
     string sError = "";

     // Set Error Message if their offhand is occupied
     if (GetIsObjectValid(oShield) == TRUE) sError = "You can't climb with your left hand occupied";

     // Set Error Message if they are carrying WAY to much gear
     if (iEncumbrance == ENCUMBRANCE_STATE_OVERLOADED) sError = "You are too encumbered to climb.";

     switch (GetBaseItemType(oPrimary))
     {
          case BASE_ITEM_DIREMACE:        iTwoHanded = 1; break;
          case BASE_ITEM_DOUBLEAXE:       iTwoHanded = 1; break;
          case BASE_ITEM_GREATAXE:        iTwoHanded = 1; break;
          case BASE_ITEM_GREATSWORD:      iTwoHanded = 1; break;
          case BASE_ITEM_HALBERD:         iTwoHanded = 1; break;
          case BASE_ITEM_HEAVYCROSSBOW:   iTwoHanded = 1; break;
          case BASE_ITEM_FALCHION:        iTwoHanded = 1; break;
          case BASE_ITEM_HEAVYFLAIL:      iTwoHanded = 1; break;
          case BASE_ITEM_QUARTERSTAFF:    iTwoHanded = 1; break;
          case BASE_ITEM_SCYTHE:          iTwoHanded = 1; break;
          case BASE_ITEM_SHORTSPEAR:      iTwoHanded = 1; break;
          case BASE_ITEM_TWOBLADEDSWORD:  iTwoHanded = 1; break;
          case BASE_ITEM_WARMACE:         iTwoHanded = 1; break;
          default:  iTwoHanded = 0; break;
     }

     if (iTwoHanded == 1) sError = "You can't climb with a two-handed weapon out.";

     int iTotalDC = GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC") + ms_CalcClimbPenalty(oClimber);

     // Give them a chance to realize the climb is TOO difficult
     if (iTotalDC > 20 + ms_CalcClimbBonus(oClimber) )
     {
          if (GetIsSkillSuccessful(oClimber, SKILL_SPOT, 18)) sError = "You feel that you won't make the climb under these conditions";
     }

     // PC Fails conditions to climb
     // Let em know and return FALSE
     if (sError != "")
     {
          FloatingTextStringOnCreature(sError, oClimber, FALSE);
          return FALSE;
     }

     // PC Passed conditions
     // Return TRUE
     return TRUE;
}

// Returns Success of an Ability
// Includes minor failure (5 or less under DC)
// and critical failure
// (termed as critical but only to differentiate between failure types)
int ms_GetAbilityCheckSuccessful(object oTarget, int iAbility, int iDC)
{
     int iCheck = GetAbilityModifier(iAbility, oTarget);

     switch(GetIsInCombat(oTarget))
     {
          case TRUE:
               iCheck = iCheck + d20();
               break;

          case FALSE:
               switch (iCheck + 10 >= iDC)
               {
                    case TRUE:  iCheck = iCheck + 10;    break;
                    case FALSE: iCheck = iCheck + d20(); break;
               }
               break;
     }

     // DEBUG
     // SendMessageToPC(oTarget, "<color=green>You rolled: " + IntToString(iCheck) +"</color>");
     // SendMessageToPC(oTarget, "<color=blue>The adjusted DC: " + IntToString(iDC) +"</color>");

     if (iCheck >= iDC) return 2;
     if (iCheck <= iDC-5) return 0;
     return 1;
}

// Handle the move to the new location.
void ms_ClimbToDestination(object oClimber)
{
     object oDestination = GetObjectByTag(GetLocalString(OBJECT_SELF, "CLIMB_DESTINATION"));

     AssignCommand(oClimber, ClearAllActions());
     AssignCommand(oClimber, ActionJumpToObject(oDestination, FALSE));
}

void ms_FallAnimation(object oClimber)
{
          AssignCommand(oClimber, ClearAllActions());
          AssignCommand(oClimber, ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 5.0));
          AssignCommand(oClimber, SpeakString("*" + GetName(oClimber) + " falls down*"));
}

Any suggestions would be greatly appreciated.


  • TheOneBlackRider aime ceci

#2
Tchos

Tchos
  • Members
  • 5 030 messages

To make an item limited-use, you don't need to add a stack script.  In the item properties, you set its Unique Power's "cost" to 1 charge per use, and in the properties set the number of Charges to whatever you want.


  • TheOneBlackRider et Sabranic aiment ceci

#3
Sabranic

Sabranic
  • Members
  • 306 messages

Hrm, the current mechanics for the script have you walk to an object in game, interact with it, and then it hauls you up to a waypoint - or dumps you on your face :D if you roll low.  The difficulty of the climb is based on some parameters you give to the environmental  object you interact with to climb.  What I'm attempting is to force a check for one of three items before it will allow you to use the interactive object.  In two cases, the game will take one of the ropes, the magical rope can be used infinitely. 

 

So based on your suggestions, I need to make the ropes charged items, give them ten to unlimited charges of the unique power, then somehow make it so the environmental objects can only be interacted with by perhaps clicking on them with the rope's spell?  That would certainly seem to make restricting them by class easier, as well as handling the charges. 

 

I am foggy on how I'd alter the script to only interact with the player after the rope is used on the environmental object however.  Trying to think of something I've seen somewhere else that is similar I can look at as an example.



#4
Tchos

Tchos
  • Members
  • 5 030 messages

Hrm, the current mechanics for the script have you walk to an object in game, interact with it, and then it hauls you up to a waypoint - or dumps you on your face :D if you roll low.  The difficulty of the climb is based on some parameters you give to the environmental  object you interact with to climb.  What I'm attempting is to force a check for one of three items before it will allow you to use the interactive object.  In two cases, the game will take one of the ropes, the magical rope can be used infinitely. 

 

So based on your suggestions, I need to make the ropes charged items, give them ten to unlimited charges of the unique power, then somehow make it so the environmental objects can only be interacted with by perhaps clicking on them with the rope's spell?  That would certainly seem to make restricting them by class easier, as well as handling the charges.

 

Well, if you want it to work by clicking on the object instead of using the item, then I guess you wouldn't use charges.  I've written scripts where you use an inventory item targeted on an object in the environment, though.  You can fire the climb checks and related scripts in such an instance, and it would only affect the one who activated it.  It can also be made to work by targeting anywhere in a certain range, instead of an object.  Like targeting anywhere along the edge instead of a convenient branch.


  • TheOneBlackRider et Sabranic aiment ceci

#5
kevL

kevL
  • Members
  • 4 052 messages
if (GetResRef(GetFirstItemInInventory(oClimber)) == "mca_rope2") iBonus = iBonus + 10;
doesn't work like that. GetFirstItemInInventory() returns literally the 1st item in oClimber's inventory. That is, the rope is probably going to be someplace else in the inventory.

If there were only one type of rope you could search the inventory for an instance, but since there are multiple types you'll likely need tag-based scripting -- where the player activates a particular rope-item, and when it's then clicked onto an object IG, it fires an OnUsed script (different than the object's OnUsed event, mind you! ). The rope's OnUsed tag-based script can then access the functions of Markshire to determine what happens ....


/iirc

And since the code would then have a particular rope-item well defined, you can do whatever you want to it, and use it to do whatever you'd want (with caveats).


ps. If googling tag-based scripting, note that NwN2 uses a refined version, by default, of the NwN1 routines.
  • Sabranic aime ceci

#6
kamal_

kamal_
  • Members
  • 5 235 messages

If you are activating this via an onUsed for the rope which then lets the player set their climbing destination (presumably via a spell like targetting system), then you will need to edit the climbing system. The Markshire system relies on known heights as variables on the object you click on to use the system, the rock at the base of the wall or whatever. So the base system won't quite work for you as the rope with some sort of dynamic targetting system as mentioned by tchos, as it wouldn't know how far to make you fall because there's nothing to tell the rope that.

 

Fortunately, I took care of that specific issue :)

My jump system is a rewrite of the Markshire Climbing System for horizontal travel, but importantly for you, I added dynamic falling calculations in the scripts based on the z axis height of the locations. You can pull that part out and have what you do calculate your falling damage automatically. There's a bunch of vector math and some use of stock functions in ways Obsidian probably didn't anticipate.

http://neverwinterva...-jumping-system

 

Wizard's Apprentice 2 also includes a Teleport Wand item, that lets you teleport by activating it and then clicking where you want to teleport to (even all the way across the map). It may be useful to see how that works.


  • Sabranic aime ceci

#7
Sabranic

Sabranic
  • Members
  • 306 messages

Well I jumped down the rabbit hole on this one.  Lol.



#8
Tchos

Tchos
  • Members
  • 5 030 messages

So the base system won't quite work for you as the rope with some sort of dynamic targetting system as mentioned by tchos, as it wouldn't know how far to make you fall because there's nothing to tell the rope that.

 

Well, when you activate a target item, it returns the location of the target (what you clicked on with the target circle, even if there's no object there), so you can just do a calculation of the distance between the PC location and the target location in your script.  But yes, it would involve modifying the climbing system.


  • Sabranic aime ceci

#9
Sabranic

Sabranic
  • Members
  • 306 messages

So what I am gathering from conversation, (correct me if I am wrong), is there exists no simple way adapt the climbing script to check for the rope items and reduce their stack size by one without a major overhaul of the pre-built code?  I have no problems with how the climbing system is implemented by its original author, my goal was to add some depth and resource management to it.  (While also creating another role for bards and rogues besides "buff-bot" and "door-lock-monkey"). 

 

I was envisioning putting some tasty side-quests and rewards in hard to reach places, where only rogues - and to a lesser degree bards - would have a chance of reaching them.  Also was thinking i could require rope use to get people out of ToH pit traps.  No rope?  Then no escape.

 

i am getting a "this will take an experienced coder 20 hours to get working" vibe from the discussion here.  It may be an impractical dream on my part if that's the case.

 

 

I always go into things with a "Plan-B" in the event "Plan-A" is unfeasible.  For example, Beta Plan involved simply having the three Rope items provide bonuses for being in the player's inventory, which I've already figured out how to do, as you can see in the code above.  Rope +2, Climbing Gear +10, Rope of Climbing +15.  I'm pretty sure the script will pay attention to class restrictions, (not 100% though, needs more testing on my part), set on the items before counting them, based on the magical object the creator made as examples. 

 

So if this is just too much work, I'll still be able to add climbing to the campaigns, it just will not as detailed as I'd like the system to be.

 

Sometimes the "perfect" has to be pushed aside for the "good enough"  when you're trying to produce a video game.  The core concept will at very least remain.

 

I've greatly appreciated everyone's input in this - as always.



#10
Tchos

Tchos
  • Members
  • 5 030 messages

Ah, no, if you want to do it with as little editing as possible to the Markshire system, you can just do the item check and decrease its stack size by one, as long as you heed kevL's words on how to obtain the object in the script.


  • Sabranic aime ceci

#11
kevL

kevL
  • Members
  • 4 052 messages
EXAMPLES

replace this

    // Silk Climbing Rope - Sabraic Addition 5/30/16
    if (GetResRef(GetFirstItemInInventory(oClimber)) == "mca_rope2") iBonus = iBonus + 10;

    // Rope of Climbing - Sabraic Addition 5/30/16
    if (GetResRef(GetFirstItemInInventory(oClimber)) == "mca_rope3") iBonus = iBonus + 15;
with this
// use the first rope found, if any
object oRope = GetFirstItemInInventory(oClimber);
while (GetIsObjectValid(oRope))
{
    if (GetResRef(oRope) == "mca_rope3")
    {
        iBonus += 15;
        // decrement oRope here.
        break;
    }

    if (GetResRef(oRope) == "mca_rope2")
    {
        iBonus += 10;
        // or here.
        break;
    }

    // question, is there "mca_rope1"

    oRope = GetNextObjectInInventory(oClimber);
}
or this
// use best rope in inventory
int iBonusTest = 0;
int iBonusHigh = 0;

object oRope = OBJECT_INVALID;

object oRopeTest = GetFirstItemInInventory(oClimber);
while (GetIsObjectValid(oRopeTest))
{
    if (GetResRef(oRopeTest) == "mca_rope3")
    {
        iBonusTest = 15;
    }
    else if (GetResRef(oRopeTest) == "mca_rope2")
    {
        iBonusTest = 10;
    }
    //else question, is there "mca_rope1"

    if (iBonusTest > iBonusHigh)
    {
        iBonusHigh = iBonusTest;
        oRope = oRopeTest;
    }

    oRopeTest = GetNextObjectInInventory(oClimber);
}

if (GetIsObjectValid(oRope))
{
    iBonus += iBonusHigh;
    // oRope can be decremented here ...
}

note that it's using resrefs instead of tags ....

The lack of "mca_rope1" indicates, to me, that the intention is to make climbing impossible without a basic rope ( or perhaps have a -30 penalty without one ).
  • TheOneBlackRider et Sabranic aiment ceci

#12
kamal_

kamal_
  • Members
  • 5 235 messages

Well, when you activate a target item, it returns the location of the target (what you clicked on with the target circle, even if there's no object there), so you can just do a calculation of the distance between the PC location and the target location in your script.  But yes, it would involve modifying the climbing system.

If you did that with the rope along a flat surface (not that you'd want to, but target circling would mean you could), you'd have a distance with no height difference and so should not take falling damage, but you'd be calculating and potentially applying falling damage based on pure x/y distance. There's the problem of failing while crossing 20 foot wide but 200 foot deep pit. Getting a correct falling damage calculation with a targeting circle is tricky. It's ultimately up to sabranic as the implementer on just how they want to handle it. I handled it via calculating the z axis difference between the start and endpoint (where failure occured, straight down) since the D20 rules for falling only talk about vertical height, they don't have any difference from falling straight down or accidentally running off a cliff and landing some distance from the base of the cliff.

 

One thing to keep in mind Sabranic is that if you use the targeting circle idea, the player who succeeds in the climb does not occupy the space in between their start position and their end position. This means the rope functions as a teleport with a given range. That can break things (teleporting over monster spawning or plot advancing triggers for example). The jump system tries to account for that by checking for various impediments such as intervening walls.


  • Sabranic aime ceci

#13
Tchos

Tchos
  • Members
  • 5 030 messages

If you did that with the rope along a flat surface (not that you'd want to, but target circling would mean you could), you'd have a distance with no height difference and so should not take falling damage, but you'd be calculating and potentially applying falling damage based on pure x/y distance. There's the problem of failing while crossing 20 foot wide but 200 foot deep pit. Getting a correct falling damage calculation with a targeting circle is tricky. It's ultimately up to sabranic as the implementer on just how they want to handle it. I handled it via calculating the z axis difference between the start and endpoint

 

Is there a reason I'm not seeing that can't be done with the targeting circle?  It returns a location complete with z axis.


  • Sabranic aime ceci

#14
Sabranic

Sabranic
  • Members
  • 306 messages

One thing to keep in mind Sabranic is that if you use the targeting circle idea, the player who succeeds in the climb does not occupy the space in between their start position and their end position. This means the rope functions as a teleport with a given range. That can break things (teleporting over monster spawning or plot advancing triggers for example). The jump system tries to account for that by checking for various impediments such as intervening walls.

 

That's a very astute point, and part of why I liked the system as the original author implemented it - a builder sets the start point and the destination, so they can prevent a player from accidentally skipping triggers, missing pot points or breaking the campaign entirely - (although now I am seeing, based on some of your dialog, there are ways around that as well).

 

 


The lack of "mca_rope1" indicates, to me, that the intention is to make climbing impossible without a basic rope ( or perhaps have a -30 penalty without one ).

 

 

Yeah, I was thinking I'd make it impossible or nearly so without a rope.  Non-rogues/bards would have a sub-standard chance even with a rope, and the difficulty can be set on the object the player interacts with to nudge the difficulty up further.

 

Thanks everyone again for the code suggestions and the conversation.  It's very instructive to see some high level people knock around ideas.



#15
Tchos

Tchos
  • Members
  • 5 030 messages

Using the targeting circle doesn't need to allow players to skip triggers.  That's all up to you and what checks you make in the script.  You could easily script it such that it checks whether "player is on east side of trigger tagged 'x'".


  • Sabranic aime ceci

#16
kamal_

kamal_
  • Members
  • 5 235 messages

Is there a reason I'm not seeing that can't be done with the targeting circle?  It returns a location complete with z axis.

It's not that it can't be done via targetting circle, it's that the targetting circle does not correctly provide information for the failure case. Getting the distance between the player and the target would produce wonky results. In the case of the 20 foot wide pit over the 200 foot drop, you'd get a distance of maybe 25 feet between the player and the target at the other end of the pit. If the player fails their climb, they don't fall 25 feet (the player->target distance), they fall 200 feet, taking 20d6 instead of 3d6. If there's no pit, the player doesn't fall 25 feet and take 3d6, they don't fall at all. The targeting circle is thus not accurate for the climb failure case unless the distance to the target is equal to the fall distance (20 feet to cross a 20 foot pit).

 

In my jump script based on Markshire climbing I had to do a bunch of vector math to conform to the d20 falling rules. You need to know the z axis height of the player, but also the z axis height of where they land if they fail the jump (or climb in this case). If the climber fails, they fall a distance that the targetting circle doesn't directly provide information on (I guess you could rule that all failures are from the midpoint of the climb).

 

In the example at bottom there's the case of where it's important to determine where the failure occurs. Half of the pit is deeper and has spikes. You'd need to determine where along the rope strung from A-B the failure occurred to correctly calculate damage from the fall. When you determine the failure point you apply a CalcSafeLocation from that point, which will return the closest valid location to the failure point (eg directly down in the pit assuming the pit is walkable ingame). Ideally the pit is walkable, so the fallen pc is trapped in the pit and you don't make a failure that results in the player not being in the pit since being stuck inside the pit is one of the problems of the pit.

 

You get the origin z axis of the player, store it temporarily in your function, then get the z axis from the CalcSafeLocation location, and calculate the falling damage based on the difference between the two. Then move the player to the calculated safe point and apply the damage to the pc. Ultimately some futzing is required if the player's fail point is determined to be in an unwalkable spot, eg did they fall into lava or water and die, or do you just rule they fell at the nearest walkable point along the A-B path.

 

There's a bunch of such stuff in the jump code, and doing the vector math requires from include functionality someone wrote because I don't think Obsidian included libraries for vector math, at least I didn't find any.

 

Behold my awesome MS Paint skillz! :P

pit.jpg


  • TheOneBlackRider et Sabranic aiment ceci

#17
Tchos

Tchos
  • Members
  • 5 030 messages

I understand all that.  That's how I would do it.  Use the z-axis information that's in the location, which is returned by the functions, to determine the falling amount.  Not using the "GetDistanceBetween" function or somesuch.  You're giving the impression that this is more difficult than it is.

 

(And a simple waypoint in the pit provides a convenient reference.)


  • Sabranic aime ceci

#18
Sabranic

Sabranic
  • Members
  • 306 messages

This is how I feel at work when our software engineer gets into a debate with our lead programmer.

 

I sit back, watch and if anyone inquires about my opinion, I try to look knowledgeable... and then ask them if they need any pretty graphics or UI development done. 

 

(Uhhhh guys... Can I solve this problem with Photoshop, Illustrator, Dreamweaver, InDesign or Flash? /Blank Stares from co-workers...)


  • ArtemisJ aime ceci

#19
Tchos

Tchos
  • Members
  • 5 030 messages

It's academic, since I know you don't want to use the method, and I agree that it's probably overkill for your situation, but I just didn't want people to come away from this without seeing its potential for a variety of applications.


  • Sabranic aime ceci

#20
Sabranic

Sabranic
  • Members
  • 306 messages

Ok Here is my shot at it:

//  Markshire Climbing System - By Thrym of Markshire  
//  Created: 3/17/07  
//  Modofied by kevl tchos Sabranic: 5/30/16                       

// ***** FUNCTION DECLARATIONS ***** 

// Determine if the PC can climb
// Fails if oClimber has anything in their right hand
// or are fully overloaded with encumbrance.
int ms_ClimbCheck(object oClimber);

// Returns the penalty (if any for oClimber)
// Heavey Encumbrance Penalty = +3
// Armor Check Penalty = +2/+4/+7 dependent
// Stealth Penalty = +5
int ms_CalcClimbPenalty(object oClimber);

// Returns the bonus (if any) for oClimber
// Climbing Gear Equipped Bonus = varies
// Climbing Magic Item (on person) Bonus = varies
// Halfling Race Bonus = +2
// Size Large or Greater Bonus = +2
int ms_CalcClimbBonus(object oClimber);

// Returns Success of an Ability
// Includes minor failure (5 or less under DC)
// and critical failure
// (termed as critical but only to differentiate between failure types)
int ms_GetAbilityCheckSuccessful(object oTarget, int iAbility, int iDC);

// Handle the move to the new location.
void ms_ClimbToDestination(object oClimber);

// Cause oClimber to animate falling down.
void ms_FallAnimation(object oClimber);


// ***** MAIN FUNCTION ***** //

void main ()
{
     object oPC = GetLastUsedBy();

     /*
     SendMessageToPC(oPC, "You have clicked on " + GetTag(OBJECT_SELF));
     int iDebugDC  = GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC");
     int iDebugHT  = GetLocalInt(OBJECT_SELF, "CLIMB_HEIGHT");
     int iDebugDIR = GetLocalInt(OBJECT_SELF, "CLIMB_DIRECTION");
     SendMessageToPC(oPC, "It's variables are...");
     SendMessageToPC(oPC, "Climb DC: " + IntToString(iDebugDC));
     SendMessageToPC(oPC, "Climb Height: " + IntToString(iDebugHT));
     SendMessageToPC(oPC, "Climb Direction: " + IntToString(iDebugDIR));     */

     if (GetLocalString(OBJECT_SELF, "CLIMB_DESTINATION") == "")
     {
          SendMessageToPC(oPC, "ERROR: Please inform the admins that this climb point has no destination. "
                                    + GetTag(OBJECT_SELF) + " in " + GetName(GetArea(OBJECT_SELF)));
          return;
     }

     // If the user is a DM just send em.
     if (GetIsDM(oPC) == TRUE || GetIsDMPossessed(oPC) == TRUE)
     {
          ms_ClimbToDestination(oPC);
          return;
     }

     // If they fail the check stop.
     if (ms_ClimbCheck(oPC) == FALSE) return;

     // Now we concern ourselves with variables
     int iDC = ms_CalcClimbPenalty(oPC) + GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC") - ms_CalcClimbBonus(oPC);
     int iHeight = GetLocalInt(OBJECT_SELF, "CLIMB_HEIGHT");
     int iDirection = GetLocalInt(OBJECT_SELF, "CLIMB_DIRECTION");

     int iDamage;
     int iAbility = ABILITY_STRENGTH;

     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_CHEST, oPC)) == "arm_climbinggear") iAbility = ABILITY_DEXTERITY;


     // Check their success.
     switch (ms_GetAbilityCheckSuccessful(oPC, iAbility, iDC))
     {
          case 0: // Critical Failure
               iHeight = iHeight > 20 ? 20 : iHeight;
               iDamage = d6(iHeight > 1 ? iHeight : 1);
               if (iDirection == 0) DelayCommand(1.5, ms_ClimbToDestination(oPC));
               DelayCommand(2.0, ms_FallAnimation(oPC));
               DelayCommand(1.5, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectDamage(iDamage), oPC));
               SetActionMode(oPC, ACTION_MODE_STEALTH, FALSE);
               break;

          case 1:  // Minor Failure
               iHeight = iHeight/2 > 20 ? 20 : iHeight/2;
               iDamage = d6(iHeight/2 > 1 ? iHeight/2 : 1);
               if (iDirection == 0) DelayCommand(1.5, ms_ClimbToDestination(oPC));
               DelayCommand(2.0, ms_FallAnimation(oPC));
               DelayCommand(1.5, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectDamage(iDamage), oPC));
               SetActionMode(oPC, ACTION_MODE_STEALTH, FALSE);
               break;

          case 2: // Success
               DelayCommand(1.5, ms_ClimbToDestination(oPC));
               break;
     }
}


// ***** FUNCTION ***** //

// Returns the penalty (if any for oClimber)
// Heavey Encumbrance Penalty = +3
// Armor Check Penalty = +2/+4/+7 dependent
// Stealth Penalty = +5
int ms_CalcClimbPenalty(object oClimber)
{
     object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oClimber);
     object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oClimber);

     int iEncumbrance = GetEncumbranceState(oClimber);

     int iPenalty = 0;

     if (iEncumbrance == ENCUMBRANCE_STATE_HEAVY) iPenalty = iPenalty + 3;

     switch (GetArmorRank(oArmor))
     {
          case ARMOR_RANK_LIGHT:  iPenalty = iPenalty + 2; break;
          case ARMOR_RANK_MEDIUM: iPenalty = iPenalty + 4; break;
          case ARMOR_RANK_HEAVY:  iPenalty = iPenalty + 7; break;
     }

     if (GetStealthMode(oClimber) == STEALTH_MODE_ACTIVATED) iPenalty = iPenalty + 5;

     return iPenalty;
}

// Returns the bonus (if any) for oClimber
// Climbing Gear Equipped Bonus = varies
// Climbing Magic Item (on person) Bonus = varies
// Halfling Race Bonus = +2
// Size Large or Greater Bonus = +2
int ms_CalcClimbBonus(object oClimber)
{
     object oItem;
     int iBonus = 0;



	 
	 
	 
	// use best rope in inventory 
	//<----- Start kevL & Sabranic Addition 6/2/16
	int iBonusTest = 0;
	int iBonusHigh = 0;

	object oRope = OBJECT_INVALID;
	object oRopeTest = GetFirstItemInInventory(oClimber);
	while (GetIsObjectValid(oRopeTest))
	{
		if (GetResRef(oRopeTest) == "mca_rope3")
		{
			iBonusTest = 15;
    	}
		else if (GetResRef(oRopeTest) == "mca_rope2")
		{
			iBonusTest = 10;
		}
		
	//else question, is there "mca_rope1"
		if (iBonusTest > iBonusHigh)
		{
			iBonusHigh = iBonusTest;
			oRope = oRopeTest;
		}
    	oRopeTest = GetNextObjectInInventory(oClimber);
	}
	if (GetIsObjectValid(oRope))
	{
		iBonus += iBonusHigh;

	// oRope is decremented here ...	
		object oRope = GetFirstItemInInventory;
		while (GetIsObjectValid(oRope))
		{
			if (GetTag(oRope) == "mca_rope1")
			{
				int iStackSize = GetItemStackSize(oRope);
				if (iStackSize == 1)
				DestroyObject(oRope);
				else
				SetItemStackSize(oRope, iStackSize - 1);
				break;
			}
			else
			if (GetTag(oRope) == "mca_rope2")
			{
				int iStackSize = GetItemStackSize(oRope);
				if (iStackSize == 1)
				DestroyObject(oRope);
				else
				SetItemStackSize(oRope, iStackSize - 1);
				break;
			}
			oRope = GetNextItemInInventory;
		}	
	}
	// <----- End Sabranic Addition 6/2/16

	
		 
	 
    // Check for items that are equipped only.	 	 
	// Climbing Gloves
     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_ARMS, oClimber)) == "glv_climbinggloves") iBonus = iBonus + 5; 
	 
     // Ring of Feather Falling (Either Hand but not both)
     if (GetResRef(GetItemInSlot(INVENTORY_SLOT_LEFTRING, oClimber)) == "rin_ringoffeatherfalling" ||
          GetResRef(GetItemInSlot(INVENTORY_SLOT_RIGHTRING, oClimber)) == "rin_ringoffeatherfalling")
          {iBonus = iBonus + 5;}

     // Check the PC for each of the possible climbing magic item resrefs.

     oItem = GetFirstItemInInventory(oClimber);
     string sResRef = GetResRef(oItem);

     while (oItem != OBJECT_INVALID)
     {
          sResRef = GetResRef(oItem);

          // if (sResRef == "resref_of_climbing_item") iBonus = iBonus + 1;

          oItem = GetNextItemInInventory(oClimber);
     }

     if (GetRacialType(oClimber) == RACIAL_TYPE_HALFLING) iBonus = iBonus + 2;
	 
     if (GetCreatureSize(oClimber) >= CREATURE_SIZE_LARGE) iBonus = iBonus + 2;
	// Check classes Bard and Thief
	// if (class_level(oClimber) >= 8) iBonus = iBonus + 5;
	// if (class_level(oClimber) >= 1) iBonus = iBonus + 5;
	 
     return iBonus;

}

// Returns the penalty (if any for oClimber)
// Based on Armor, Encumbrance, and type of gear in hand
int ms_ClimbCheck(object oClimber)
{
     object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oClimber);
     object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oClimber);
     object oPrimary = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oClimber);

     int iEncumbrance = GetEncumbranceState(oClimber);
     int iTwoHanded;
     string sError = "";

     // Set Error Message if their offhand is occupied
     if (GetIsObjectValid(oShield) == TRUE) sError = "You can't climb with your left hand occupied";

     // Set Error Message if they are carrying WAY to much gear
     if (iEncumbrance == ENCUMBRANCE_STATE_OVERLOADED) sError = "You are too encumbered to climb.";

     switch (GetBaseItemType(oPrimary))
     {
          case BASE_ITEM_DIREMACE:        iTwoHanded = 1; break;
          case BASE_ITEM_DOUBLEAXE:       iTwoHanded = 1; break;
          case BASE_ITEM_GREATAXE:        iTwoHanded = 1; break;
          case BASE_ITEM_GREATSWORD:      iTwoHanded = 1; break;
          case BASE_ITEM_HALBERD:         iTwoHanded = 1; break;
          case BASE_ITEM_HEAVYCROSSBOW:   iTwoHanded = 1; break;
          case BASE_ITEM_FALCHION:        iTwoHanded = 1; break;
          case BASE_ITEM_HEAVYFLAIL:      iTwoHanded = 1; break;
          case BASE_ITEM_QUARTERSTAFF:    iTwoHanded = 1; break;
          case BASE_ITEM_SCYTHE:          iTwoHanded = 1; break;
          case BASE_ITEM_SHORTSPEAR:      iTwoHanded = 1; break;
          case BASE_ITEM_TWOBLADEDSWORD:  iTwoHanded = 1; break;
          case BASE_ITEM_WARMACE:         iTwoHanded = 1; break;
          default:  iTwoHanded = 0; break;
     }

     if (iTwoHanded == 1) sError = "You can't climb with a two-handed weapon out.";

     int iTotalDC = GetLocalInt(OBJECT_SELF, "CLIMB_BASE_DC") + ms_CalcClimbPenalty(oClimber);

     // Give them a chance to realize the climb is TOO difficult
     if (iTotalDC > 20 + ms_CalcClimbBonus(oClimber) )
     {
          if (GetIsSkillSuccessful(oClimber, SKILL_SPOT, 18)) sError = "You feel that you won't make the climb under these conditions";
     }

     // PC Fails conditions to climb
     // Let em know and return FALSE
     if (sError != "")
     {
          FloatingTextStringOnCreature(sError, oClimber, FALSE);
          return FALSE;
     }

     // PC Passed conditions
     // Return TRUE
     return TRUE;
}

// Returns Success of an Ability
// Includes minor failure (5 or less under DC)
// and critical failure
// (termed as critical but only to differentiate between failure types)
int ms_GetAbilityCheckSuccessful(object oTarget, int iAbility, int iDC)
{
     int iCheck = GetAbilityModifier(iAbility, oTarget);

     switch(GetIsInCombat(oTarget))
     {
          case TRUE:
               iCheck = iCheck + d20();
               break;

          case FALSE:
               switch (iCheck + 10 >= iDC)
               {
                    case TRUE:  iCheck = iCheck + 10;    break;
                    case FALSE: iCheck = iCheck + d20(); break;
               }
               break;
     }

     // DEBUG
     // SendMessageToPC(oTarget, "<color=green>You rolled: " + IntToString(iCheck) +"</color>");
     // SendMessageToPC(oTarget, "<color=blue>The adjusted DC: " + IntToString(iDC) +"</color>");

     if (iCheck >= iDC) return 2;
     if (iCheck <= iDC-5) return 0;
     return 1;
}

// Handle the move to the new location.
void ms_ClimbToDestination(object oClimber)
{
     object oDestination = GetObjectByTag(GetLocalString(OBJECT_SELF, "CLIMB_DESTINATION"));

     AssignCommand(oClimber, ClearAllActions());
     AssignCommand(oClimber, ActionJumpToObject(oDestination, FALSE));
}

void ms_FallAnimation(object oClimber)
{
          AssignCommand(oClimber, ClearAllActions());
          AssignCommand(oClimber, ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0, 5.0));
          AssignCommand(oClimber, SpeakString("*" + GetName(oClimber) + " falls down*"));
}

I am getting an "Undefined Identifier" here:

oRopeTest = GetNextObjectInInventory(oClimber);

Stackoverflow suggests that it's one of the following issues:

- Missing Header.

- Missing Variable.

- Incorrect Scope.

- Use before a declaration.

 

But it looks to me like oRopeTest and oClimber are defined.


  • TheOneBlackRider aime ceci

#21
Sabranic

Sabranic
  • Members
  • 306 messages

It's academic, since I know you don't want to use the method, and I agree that it's probably overkill for your situation, but I just didn't want people to come away from this without seeing its potential for a variety of applications.

 

I find the whole conversation fascinating, and am doing my level best to learn something from it.  I'm nothing short of amazed at how powerful the NWN2 toolset actually is.  This thread and the spell component topic opened my eyes a bit... I would not have even thought to consider the amount of detail the game is actually capable of tracking - and the level of manipulation someone can get into if so inclined - and technically adept enough. 

 

Safe to say that of the few who actually develop for this game, only a tiny fraction even begin to touch the power that's really driving the product. 

 

It also makes me wonder why so much of this potential was untouched by Obsidian.  Things like teleportation, climbing, and a few other odds'n ends are staples of RPG's since the gold-box era - the tool-set was clearly up to the task, I wonder why the developers never exploited it?  Time perhaps?



#22
Tchos

Tchos
  • Members
  • 5 030 messages

I try to stress to people as much as I can that you can put virtually anything you want an RPG to have into this game.  You can make almost anything happen.


  • kamal_ et Sabranic aiment ceci

#23
kevL

kevL
  • Members
  • 4 052 messages

I am getting an "Undefined Identifier" here:

oRopeTest = GetNextObjectInInventory(oClimber);


typo in my Example above.

currently refactoring and putting in mca_rope1 ...


If I wasn't heavily laden in another project I'd be chipping away on Xoreos. NwN2 has problems and software technology has developed beyond 2006.
  • Sabranic aime ceci

#24
Sabranic

Sabranic
  • Members
  • 306 messages

 

xoreos is an open source implementation of BioWare's Aurora engine and its derivatives, licensed under the terms of the GNU General Public License version 3 (or later). The goal is to have all games using this engines working in a portable manner, starting from Neverwinter Nights and ending with Dragon Age II.

Games

The following games are valid targets for xoreos:

 

Holy crap...


  • ArtemisJ aime ceci

#25
ArtemisJ

ArtemisJ
  • Members
  • 127 messages

Holy crap...

 

I just pray he isn't hit with some copyright mumbo jumbo..

Creating a open source / hookable engine would go miles long to making NWN1 / NWN2 last a long time.