Robert Straughan - Capture Glory: Minotaur Death
Granthos When the player leaves the caves, I want them to meet Granthos the minotaur. Since Granthos is probably too high a level for the player to beat, even though I've made them level 3, I'm going to rig the fight. (I'd like to just point out that I've actually seen Kilinar win this fight, so it is possible for the player to defeat Granthos by themselves. However, since I wanted to kill Kilinar and bring in Alluen, I decided to go ahead and rig it anyway.) When the player gets to the exit, I want them to stop and be forced to talk to Granthos. To do this, I use the following script: //Script "cave_triggerboss" void main() { object oEnter = GetEnteringObject(); AssignCommand(oEnter, ClearAllActions()); SetCommandable(FALSE, oEnter); if (GetIsPC(oEnter) && GetLocalInt (OBJECT_SELF, "FIRED") == 0) { SetLocalInt(OBJECT_SELF, "FIRED", 1); object oBoss = CreateObject(OBJECT_TYPE_CREATURE, "minotaur001", GetLocation(GetObjectByTag("DOOR_SPAWN")) ); AssignCommand(oBoss, ActionMoveToObject(GetObjectByTag("CAVE_WALK_TO")) ); DelayCommand(3.0f, AssignCommand(oBoss, ActionStartConversation (oEnter)) ); } } If you read through this, you'll notice nothing that I haven't done before. Declare the object that triggered this handler (which is an OnEnter handler for a trigger I've placed so the player must cross it before reaching the exit). I've assigned a command to that object (since I don't want the caller to do it) that tells it to stop everything it's doing. I've then told it that it can no longer take any actions. I don't know whether the object involved was a player or not (it might be Kilinar), and I also don't want this to fire more than once. In which case, I have a conditional statement that looks to see if this has been done once, and if the entering object was a player. Provided the checks are both true (because of the logical AND), I make sure the loop won't happen again, and then create the minotaur. Notice two things, first, I haven't declared or initialised either the location or the waypoint prior to this function, I've just slotted them straight in. Second, I used the resref of the creature to create it, since it is in the custom palette. Neither of these should be particularly difficult to fathom out at this point, so if you have difficulty understanding that line, try some of the previous tasks again. Notice also that I have actually declared the newly created object. Rather than using the function as though it were a void function, I've given it a name. This is so I can immediately give it command in this same script. The next two lines shouldn't be difficult either, using AssignCommand and DelayCommand to tell the minotaur what to do. Notice I've delayed the second command by 3 seconds, to ensure that the actions are stacked in the queue in the correct order.
Go Kilinar! Go! So, now that I have my boss creature, and the player can't do anything but talk to them, I create a conversation to use. At this stage, the player may or may not have Kilinar with them. If they don't, I get rid of the Kilinar object, to prevent confusion. //Script "cave_bosscon1" int StartingConditional() { object oDwarf = GetObjectByTag("HENCH_DWARF"); if (GetMaster(oDwarf) == GetPCSpeaker()) return 1; DestroyObject(oDwarf); return 0; } This is a starting conditional statement for a conversation. Notice the order this does things in. We get the dwarf, and if they are the henchman to the speaker, we allow the node to be spoken. If the statement was not true, the script will continue on, and destroy the dwarf. It will then return zero, meaning the line won't fire. So all I do is attach this to the line that responds assuming the dwarf is in your party, and place it above the one where he isn't. //Script "cave_bosscon2" #include "nw_i0_generic" void main() { object oDwarf = GetObjectByTag("HENCH_DWARF"); SetCommandable(TRUE, oDwarf); RemoveHenchman(GetMaster(oDwarf), oDwarf); AdjustReputation(OBJECT_SELF, oDwarf, -100); AdjustReputation(oDwarf, OBJECT_SELF, -100); AssignCommand(oDwarf, DetermineCombatRound()); DetermineCombatRound(); } Nothing new here in terms of the scripting, but there's a nice little trick I've done here. This script is part of the node that fires when the dwarf is found to be in the PC's party. It gets him, and makes him commandable (since he might have made uncommandable by the trigger). I then remove him from the player's party. I could have specified GetPCSpeaker for the first parameter of the RemoveHenchman function, but instead I used GetMaster. This is a nice line to bare in mind, since it can be used anywhere to remove the target from any party. We then do a bit of manipulation of reputations. If you've been fiddling with the editor, you will have noticed factions. I've explained them a bit in the box, but otherwise, these functions make the minotaur and the dwarf hostile towards each other. I've then gone and used a function in the final two lines that you won't find in the function list. So how can I use it? Remember custom functions? Remember how #include works? The file this script includes, nw_i0_generic, is Bioware's extra function library. If you can't find a function in the list of functions that does what you want, before writing it yourself, check to see if the function already exists in this file. By #including the file, I now have access to all the functions in that file. Note that any script which #includes this file will take noticeably longer to compile than normal. That's because the file is a very large script. In this instance, I've used DetermineCombatRound. While most people find it has different uses, purposes or results, I've always found that by using it on an object, it causes that object to determine the best course of action to take in combat. So, when this conversation ends, the dwarf and the minotaur will have a fight. However, I know the dwarf can win on occasion, so I want to be sure that he dies. To do this, I've modified his OnPhysicalAttacked script to the following: //Script "cave_insurance" void main() { if (GetTag(GetLastAttacker()) == "BOSS_GRANTHOS") DelayCommand (3.0f, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), OBJECT_SELF ) ); /* rest of script */ What does it do? If the last attacker was Granthos, then the dwarf will die. Simple. Note that I could have left this script intact, and used a User Defined Event (read the box). Notice that I place a DelayCommand on the death effect. This is because the OnPhysicalAttacked handler fires the instant the object is attacked, which wouldn't look too good. By delaying the death by 3 seconds, the attack animation will have time to play out. I then put into the OnDeath script handler for Kilinar the following: //Script "cave_diedwarf" void main() { object oKiller = GetLastHostileActor(); if (GetTag(oKiller) == "BOSS_GRANTHOS") { object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC ); SetLocalInt(oKiller, "DWARF_DIED", 1); AssignCommand(oKiller, ClearAllActions()); AssignCommand(oKiller, ActionStartConversation(oPC)); AddJournalQuestEntry("HenchKilinar", 40, oPC); } } What does this do? GetLastHostileActor gets the last person to do anything vaguely hostile towards the caller, such as attack, or cast a spell. I could have used GetLastAttacker, given the handler I'm using, but I've chosen this one (it's more accurate in case the object was killed by means other than being physically attacked). If the last person to be hostile towards the dwarf turns out to be Granthos, I get hold of the player using the GetNearestCreature function and the right criteria. The local variable I set is to let the minotaur's conversation know the dwarf has died. Somewhere in the conversation, you'll find a node with the following script: //Script "cave_bosscon4" int StartingConditional() { int iResult; iResult = GetLocalInt(OBJECT_SELF, "DWARF_DIED") == 1; return iResult; } This is a standard StartingConditional script which checks against that variable to see if the dwarf has died or not. This is a matter of how I've ordered the conversation nodes in the conversation. As for the rest of the script, we tell the killer of the dwarf, which we know is Granthos, to stop what he's doing, and talk to the nearest player. In addition, we advance the journal entry for the dwarf to the one where he's dead. The last script attached to the minotaur's conversation is the following one: //Script "cave_bosscon3" void main() { SetCommandable (TRUE, GetPCSpeaker()); object oAlluen = CreateObject(OBJECT_TYPE_CREATURE, "alluen", GetLocation(GetObjectByTag("DOOR_SPAWN")) ); object oGranthos = OBJECT_SELF; AdjustReputation(OBJECT_SELF, oAlluen, -100); DelayCommand(2.0f, AssignCommand(oAlluen, ActionSpeakString("Not so fast, Granthos!") ) ); DelayCommand(2.3f, AssignCommand(oAlluen, ActionAttack(oGranthos)) ); } This is where the player is made free again. Also, we create Alluen, an NPC, near the doorway (where I've placed the waypoint that I'm using in this script). I've then adjusted the way Alluen feels about Granthos, and setup her action queue to do two things. First, actually say something to Granthos, and second, to actually attack Granthos. Notice that I've declared oGranthos as OBJECT_SELF. Why have I done this? Becasue when I AssignCommand, I am changing the caller of a function. OBJECT_SELF refers to the caller, so by having OBJECT_SELF directly in the Attack function, I would actually be telling Alluen to attack herself. To avoid this, I have declared OBJECT_SELF while Granthos is the object calling this script, and then referred to this declaration in the function. That way, I know for certain I am telling her to attack Granthos. Since I want Granthos to die, I'm going to place the same kind of script in his OnPhysicalAttacked as I did with Kilinar: //Script "cave_dieboss" void main() { if (GetTag(GetLastAttacker()) == "HENCH_ELF") DelayCommand(3.0f, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), OBJECT_SELF ) ); /* rest of script */ So, by adding these two lines into the script, I've got the same effect as I did with Kilinar, only it checks to see if it was Alluen instead of Granthos (since he couldn't kill himself... theoretically).
Next! This entire section was about creating a sequence of events. I had the minotaur enter the area, and speak with the player. If the dwarf was there, the two had a fight, which the minotaur won. Then the elf showed up and slew the minotaur. If you didn't keep up, I suggest looking at how the minotaur's conversation is setup, since nearly all of the order in which this scripting occured was handled by that one file. When you're ready, move onto the next section.
Tasks I haven't been totally efficient in my scripting here. Try re-writing the scripting for Granthos and Kilinar to have the extra lines that I placed in there fired by their OnUserDefined handler (you'll need to read the box if you don't know how to do this). Better yet, re-write the scripting for all three NPCs so that they use their plot flag to prevent the NPC that we want to remain alive from dying. This way the fight will be longer, and more realistic.
Hint
|
|
||
Screenshots |
|||
author: Robert Straughan, editor: Charles Feduke