Jump to content

BG1 coding tutorial


Recommended Posts

Edit: I notice there are two GLOBALs called before the CreateCreature command. I don't find them created or set anywhere. Are they created when called, or do I need to create the GLOBALs somewhere?
Game variables are assumed to be 0 if they haven't been set. All I can think is maybe the CRE isn't getting copied properly, or maybe BG1 doesn't like it for some reason, but I doubt it.

 

Also, this code makes no sense at all to me:

  COPY_EXISTING ~ar2301.bcs~ ~override~
			~ar4809.bcs~ ~override~
			~ar0705.bcs~ ~override~

It does nothing, except possibly fail if the scripts don't exist. Whereas EXTEND_BOTTOM will automatically create the scripts if they don't exist, so you don't need the COPY_EXISTING. In BG1, the area scripts should be set in the .are files, so that shouldn't be a problem either.

Link to comment

Thanks for the explanation, Miloch. The tp2 is updated.

 

I finally got the bard to spawn in an unmodded game. I was running Zed's area check emulation, and when I removed it he appeared. I tried it in a modded game (with the area check emulation running) and no spawn occurs. I'll do some more checking, see if installing before the emulation to see if it will run if it's before the emulation in the baf file or not. If the former, then I can still incorporate it, but if the latter, then there may be a problem to work out.

 

Edit: It seems my character spawning is blocked by the area check emulator somehow - if it's after the code in the bcs file. It runs fine if before. It's something that may need looking into. Can someone validate this result, or get the opposite result?

Edited by grogerson
Link to comment
EscapeAreaMove(S:Area*,I:X*,I:Y*,I:Face*) does not exist. A very simple work around for unjoinable NPCs would be, to destroy the creature at one place and recreate it at another, using for example

~MoveToPoint(), Destroyself()~ and then ~CreateCreature()~ at the new location (remember that possible local variables of the first encounter are lost then, like "NumTimesTalkedTo").

Zed Nocear suggests the following for BG1:TotSC: ~RunAwayFrom(LastTalkedToBy,60) LeaveAreaLUA("AR4801","",[284.454],0)~

I don't know whether LeaveAreaLUA() would work in BG1 (without TotSC), though.

I can't get "LeaveAreaLUA" to work for a non-party member in BG1:TotSC. Anyone having any experience with this?

Link to comment
I can't get "LeaveAreaLUA" to work for a non-party member in BG1:TotSC. Anyone having any experience with this?
I hope it works, because that sucks if not. It seems to be used in BG1V but only in cutscenes - e.g. bragecut.bcs has LeaveAreaLUA("AR4802","TRBRACAP",[325.469],12). But again, these all seem to change the focus to players rather than NPCs. Not sure if there's another way to get a creature to change areas in BG1 other than EscapeArea() or DestroySelf() and recreating it in another area (assuming even that works). At least it works for the party - I would give up on all this hackery to get something working for BG1 if that didn't work.

 

In the progress of looking at this, I figured out what that second parameter "parchment" is. It points to a game-transition-like parchment in the form of a .mos file. In this case, trbracap.mos is a parchment of presumably Brage surrendering to someone, so that's what it can display during cutscene area changes.

Link to comment

Eridan reports that dialogue names for NPCs have to be not more than six characters long in BG1. I wasn't aware of this.

actually I was going to report similar however I found it to be no more than 7 characters if you include the level #. also there must be a base character without a level # otherwise there will be a crash. (at least this has been my experience....)

 

I've also had difficulties with one version in one area intended to be non-joinable with one dialog and another set of creature files intended to be joinable in a different area (similar to what Imoen does). Even tho the proper character file was indicated in the GAM file, I would continue to get initial dialog from the non-joinable version. Easiest solution was to combine the dialog files and adjust states and use variables so the proper states would trigger at the proper time.

 

(Should note that it is a Tutu mod NPC that I'm converting and there wasn't an NPCLEVEL.2da adjustment done. Wasn't till I'd already merged the dialog files that I learned about the NPCLEVEL.2da file. Perhaps that would have solved the problem for me instead. However, what I have works... so far.)

 

I also took the code snip-it to add an npc to the game and put it into a tph as a function. Perhaps it could be added to weidu and the older ADD_GAME_NPC removed? Perhaps with a better name LOL

DEFINE_ACTION_FUNCTION ~ab_ADD_1_GAME_NPC_BG~ BEGIN
//Zed's method but in a function wrapper...
COPY_EXISTING ~baldur.gam~ ~override~
 INSERT_BYTES 0xB4 0x160
 WRITE_LONG 0x140 0xFFFFFFFF
 WRITE_LONG 0x144 0xFFFFFFFF
 WRITE_LONG 0x168 0xFFFFFFFF
 WRITE_SHORT 0x16C 0xFFFF
 READ_LONG 0x34   npc_count
 WRITE_LONG 0x34  ("%npc_count%" + 1)
 READ_LONG 0x20   party_offset
 WRITE_LONG 0x20  ("%party_offset%" + 0x160)
 WRITE_LONG 0x28  ("%party_offset%" + 0x160)
 WRITE_LONG 0x50  ("%party_offset%" + 0x930)
 WRITE_EVALUATED_ASCII 0xC0 ~%NPC_filename%~	  //npc file name
 WRITE_LONG 0xC8  %NPC_facing%		  //npc facing direction
 WRITE_EVALUATED_ASCII 0xCC ~%NPC_startArea%~	 //npc start area
 WRITE_SHORT 0xD4 %NPC_XCoord%		  //npc x coord
 WRITE_SHORT 0xD6 %NPC_YCoord%		  //npc y coord
BUT_ONLY
END
//example usages:
//one line per
/*
LAUNCH_ACTION_FUNCTION ~ab_ADD_1_GAME_NPC_BG~
				   INT_VAR
				   NPC_facing = 6
				   NPC_XCoord = 376
				   NPC_YCoord = 178
				   STR_VAR
				   NPC_filename = ~AMORTH1~
				   NPC_startArea = ~AR3308~
				   END
*/
//one line for the whole thing
/*
LAUNCH_ACTION_FUNCTION ~ab_ADD_1_GAME_NPC_BG~ INT_VAR NPC_facing = 6 NPC_XCoord = 376 NPC_YCoord = 178 STR_VAR NPC_filename = ~AMORTH1~ NPC_startArea = ~AR3308~ END
*/
//either way works

 

One thing that bothers me about the code from Zed is that it doesn't have the typical read the offset and count and jump to the right spot etc that we normally do for items, creatures, areas etc... however since it adds only 1 npc and at the front of the list, the locations will always be the same even tho they are in relation to the beginning of the file. Guess I'm used to having to be aware that what I want to do could have been previously adjusted by another mod.

Edited by plainab
Link to comment

out of curiosity how do the the bioware originals in BG/BG:ToTSC perform their banters? I've not seen any scripted method to get them to talk with each other, they just do. Is there some sort of internal banter engine that is hard wired for only the bioware originals?

 

I've only ever gotten the fight between Jaheria/Khalid & Xzar/Montaron once and I interrupted it cause I didn't know what it was. LOL been trying to get it to repeat and there is no noticeable way to get them to do so.

 

anywho... off to figure out how to convert tutu style banters into BG:ToTSC style... i think i have a nightmare ahead of me :p

 

***************************

on interjections... after the compiling does the 'dump' file actually get used or does it end up being left over cruft? If it's left over cruft then my little idea to try and make a function for a more streamlined process has merit. If it's actually used in game, then I can't do what I'm thinking of doing....

Edited by plainab
Link to comment

I believe I have successfully streamlined the ICT workaround and in the process removed the need for a 'stupid passback' line

 

to quote Kulyok "watch my hands closely" :p

 

Example will be in regards to using an ICT from Tutu Finch in BG:ToTSC

 

First we need to establish whether or not Finch can talk. Best thing to do is let Finch tell us this, so we add a few blocks to her override script file

IF
 StateCheck("sufinch",CD_STATE_NOTVALID)
 InParty("sufinch")
 Global("abFinchCanTalk","GLOBAL",1)
THEN
 RESPONSE #100
   SetGlobal("abFinchCanTalk","GLOBAL",0)
   Continue()
END
IF
 !InParty("sufinch")
 Global("abFinchCanTalk","GLOBAL",1)
THEN
 RESPONSE #100
   SetGlobal("abFinchCanTalk","GLOBAL",0)
   Continue()
END
IF
 InParty("sufinch")
 Detect("sufinch")
 !StateCheck("sufinch",CD_STATE_NOTVALID)
 Global("abFinchCanTalk","GLOBAL",0)
THEN
 RESPONSE #100
   SetGlobal("abFinchCanTalk","GLOBAL",1)
   Continue()
END

Since variables are value 0 if never set what this does is if Finch never joined the party the variable "abFinchCanTalk" will automatically be = 0 and so when Finch joins we change that value to = 1. So after she has joined, if she leaves the party it gets reset to 0 and if she becomes unable to talk it also gets set to 0.

If you think about it, whenever this value is 0 there is no need to check and see if she is actually present via Detect(), See() or Exists(). She only needs to be there for her interjection to work. She can be there outside of the party, or in an unable to talk state, her interjection will still not be triggered.

 

Enter the workhorse. This is a function which will insert one triple file workaround ICT at a time. You can launch the function & define the variables directly in the TP2 or use an external TPH, your call

DEFINE_ACTION_FUNCTION ~ab_BG_Interject~ BEGIN
ACTION_IF (FILE_EXISTS_IN_GAME ~%ab_dialog%.dlg~) THEN BEGIN
<<<<<<<< dump.d
BEGIN ab_dump
IF ~~ 0   SAY ~%ab_tempDialogText%~ IF ~~ THEN EXIT END
>>>>>>>>
<<<<<<<< replace.d
REPLACE ab_dump IF ~~ THEN 0   SAY ~%ab_tempDialogText%~ COPY_TRANS %ab_dialog% %ab_statenum%  END END
ADD_TRANS_TRIGGER %ab_dialog% %ab_statenum%  ~%ab_canTalkVarAt0%~
>>>>>>>>
<<<<<<<< interjections.d
EXTEND_BOTTOM %ab_dialog% %ab_statenum%
IF ~%ab_pos_trigger% %ab_interjectVarAt0%~ EXTERN %ab_npc_dialog% ab_newstate
END
CHAIN %ab_npc_dialog% ab_newstate
%ab_interject%
END
COPY_TRANS ab_dump 0
>>>>>>>>
 COMPILE dump.d  EVALUATE_BUFFER
 COMPILE replace.d  EVALUATE_BUFFER
 COMPILE interjections.d  EVALUATE_BUFFER
END
END

 

Here is an example of launching the function & defining the variable

INCLUDE ~finch/lib/ab_BG_Interject.tph~
//since weidu doesn't like multiple sets of ~ & " together need to assign certain global variables outside of the LAF definitions
OUTER_SPRINT ab_interjectVarAt1 ~Global("abFinchTalkedAlatos","GLOBAL",1)~
LAUNCH_ACTION_FUNCTION ~ab_BG_Interject~
   INT_VAR
   ab_statenum = 0 //---------------------------------------------------- state number of the state that is being interjected into
   STR_VAR
   ab_tempDialogText = Finch //------------------------------------------ use npc's name so don't make another strref for crap
   ab_dialog = ALATOS //------------------------------------------------- file name of dialog to be interjected into
   ab_interjectVarAt0 = ~Global("abFinchTalkedAlatos","GLOBAL",0)~ //---- global variable to indicate the interjection has not happened
   ab_canTalkVarAt0 = ~Global("abFinchCanTalk","GLOBAL",0)~  //---------- npc cannot interject  set via npc's script
   ab_pos_trigger = ~InParty("sufinch")
				 Detect("sufinch")
				 !StateCheck("sufinch",CD_STATE_NOTVALID)~ //--------- positive checks by npc being spoken to to make sure that joined npc is present
   ab_npc_dialog = SUFINCHJ //------------------------------------------- file name of dialog file used by joined npc
   ab_interject = EVALUATE_BUFFER "@813 DO ~Set%ab_interjectVarAt1%~" //- list of text & actions that make up the chain part of the interjection
END

You only need to INCLUDE the definition once. It could be in the ALWAYS section of the TP2 or as I did it via an external tph

 

The resultant output of the state in question from the decompiled alatos.dlg after installation:

IF WEIGHT #0 ~NumTimesTalkedTo(0)
~ THEN BEGIN 0 // from:
 SAY #2783 /* ~Welcome my little friends!  Please relax, and keep your weapons at your sides.  No need for hostility.~ */
 IF ~  Global("abFinchCanTalk","GLOBAL",0)
~ THEN DO ~SetGlobal("TalkedToThief","GLOBAL",1)
SetGlobal("TalkedToAlatos","GLOBAL",1)
~ GOTO 1
 IF ~  InParty("sufinch")
Detect("sufinch")
!StateCheck("sufinch",CD_STATE_NOTVALID)
Global("abFinchTalkedAlatos","GLOBAL",0)
~ THEN EXTERN ~SUFINCHJ~ 68
END

As you can see, If Finch said she couldn't talk OR she never joined the party then the original transition occurs. IF she is in the party she will do her bit. Note I could have used the can talk variable at value 1 along with the Detect() check. However, I wanted the talking NPC to check for themselves since due to needing to use Continue() on the blocks in Finch's override script there could be a possible delay in the proper variable being set. Therefore the NPC checks for themselves at the moment of need.

 

Feel free to nitpick it apart.

 

I even checked it out by adding in a fake interjection for a fake npc name Fred. Everything looked good in that, if both Fred & Finch had their talk variable at 0 the original state would fire. If Fred's state was at 0 and Finch was present and able to talk Finch would do hers. If Fred was present & able to talk he would do his. Only concern I see (and would be true even outside of the function) if both Fred & Finch are there. Only first one installed gets to say their banter....

Perhaps it is a good thing... idk.

Link to comment

Thank you, plainab, I will comment if I took the time to read it carefully.

 

-

 

3. Actions

 

DestroyItem() doesn't seem to be executed if triggered via script. In BG1 (+TotSC), no DestroyItem is used via script, and it doesn't work if used, either. Has to be used via dialogue transaction.

 

Can anyone confirm?

Edited by jastey
Link to comment

What I can confirm is that something like this:

 

CHAIN
IF ~~ THEN C#Q01001 alanna_04
@12
== %IMOEN_JOINED% IF ~Global("C#BGQE_NPCReactions","GLOBAL",1)
InParty("IMOEN") Detect("IMOEN") !StateCheck("IMOEN",CD_STATE_NOTVALID)~ THEN @111
== %JAHEIRA_JOINED% IF ~Global("C#BGQE_NPCReactions","GLOBAL",1)
InParty("JAHEIRA") Detect("JAHEIRA") !StateCheck("JAHEIRA",CD_STATE_NOTVALID)
InParty("IMOEN") Detect("IMOEN") !StateCheck("IMOEN",CD_STATE_NOTVALID)~ THEN @112
END
++ @70 DO ~SetGlobal("C#Q01_TalkedToAlanna","GLOBAL",2)~ + alanna_04_2
++ @71 DO ~SetGlobal("C#Q01_TalkedToAlanna","GLOBAL",2)~ + alanna_04_1
++ @72 DO ~SetGlobal("C#Q01_TalkedToAlanna","GLOBAL",2)~ EXIT

 

Doesn't work as intended: The interjections of the NPCs don't play. The reply options are shown directly, and the button sais "continue", but the interjections don't play. The CHAIN has to end with transitions only, no reply options, then the interjections play as intended.

 

This is also true if an extra line is added via I_C_T, which actually functions for BG1 under some special cases, as to: No reply options (same problam as above) and best only one transition, e.g. this works:

 

I_C_T %tutu_var%COKSMTH 3 C#Q04_WyvernDeliverer
== %tutu_var%COKSMTH @0
END

The original state from the COKSMTH is:

IF ~~ THEN BEGIN 3 // from: 1.2 0.2
 SAY #10820 /* ~I see that you have no use for beating around the bush. Well, so be it. I cast aside my master woodsman facade. You have interrupted my little wyvern-training session and likely set my schedule back by days. I have worked long and hard to gain their trust, but if I they are to be ready for duty at the mine I shall have to placate these beasts with meat! Fight, for you shall die if you lose!~ */
 IF ~~ THEN DO ~Enemy()
~ EXIT
END

Edited by jastey
Link to comment

I have to take the last thing back, at least for now, as I have the problem that

 

I_C_T %tutu_var%drunk 4 c#q12_Drunk
== %tutu_var%drunk @0
END

 

Will not compile with the error message "Cannot process COPY_TRANS". Anyone having an idea why I_C_T works on one dlg, but not on another, please let me know.

Link to comment
DestroyItem() doesn't seem to be executed if triggered via script. In BG1 (+TotSC), no DestroyItem is used via script, and it doesn't work if used, either. Has to be used via dialogue transaction.
Pretty sure it works, both in creature and area scripts, e.g.:

 

ActionOverride("t-ealswi",DestroyItem("t-minhp1")) in an area script, or simply

 

DestroyItem("t-minhp1") in a creature script.

Link to comment

I used DestroyItem() in the dplayer3.bcs and it was not processed. Everything else in that script block was (creation of a cre and journal entry). I changed the code now so a dialogue pops up (which is better for my purpose) so I would have to recode to check but I did it because the item destruction didn't work in several tries. (Item was in the inventory etc. pp)

 

 

Why do I get a "Reply to this topic" window if I cannot post as a guest?

Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...