Author Topic: BG1 Coding Tutorial - Differences to the BGII Engine  (Read 18573 times)

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
BG1 Coding Tutorial - Differences to the BGII Engine
« on: April 06, 2008, 05:29:51 AM »
BG1 coding tutorial
This tutorial assumes a certain knowledge of the BGII syntax and IE-modding in general. It highlights some crucial differences between the engines of BG2 and BG1, especially with regards to the restrictions of the BG1 engine in comparison to that of BG2, but it also includes some available BG1 syntax that might not be readily apparent.
The focus of this tutorial is on the workarounds and simulations that emulate BG2 functionality.

Comments, corrections, and additions are welcome, especially if you can provide not yet listed workarounds for missing BGII functions, please feel free to post them here. I will update the tutorial accordingly.

Thank you to berelinde for language corrections!

notation:
BG1 - plain BG1, without the Tales of the Sword coast Add-on.

BG1:TotSC - BG1 with the add-on installed.

cre: creature file used in IE-modding

itm: item file used in IE-modding

The IESDP is always a good source, but for BG1 triggers and actions, it only reflects BG1:TotSC, and some of the used examples contain BGII only syntax.

Content

1. General

BAMs; There is no overall script like "Baldur.BCS" in BG1; cre and itm; No dialogues at rest; "Greeting sound"; Coding interjections; Items with charges; Add mod NPC to BG1; Post-dialogue of joinable NPCs; Player initiated dialogue; Portraits; No real time timers; Variables; Setting of variables in dialogues and scripts - how fast are the new values available?;

2. Triggers

Alignment triggers; AreaCheck(); AreaType(); Class check; CombatCounter(0); IsGabber(O:Object*); Level(), including LevelGT() and LevelLT(); OR(); PartyRested(); XP(), including XPGT() and XPLT()


3. Actions

ApplySpellRES(); CreateCreature(); CreateCreatureObjectOffScreen(); CreateItem(); CreateVisualEffectObject(); EraseJournalEntry(); EscapeAreaMove(S:Area*,I:X*,I:Y*,I:Face*); FaceObject(); FindTraps(); GivePartyGoldGlobal(S:Name*,S:Area*); Journal entries: JOURNAL and AddJournalEntry(); 107 MoveToOffset(P:Offset*); RestParty(); SetPlayerSound()

4. Spell Effects

#122 (0x7a) Item: Create Inventory Item [122]


5. Tokens


Links used in the Tutorial

AND and OR in scripting, How to simulate them in various IE games

BAMWorkshop II (scroll down the page for download link)

BG1 CREATURE SOUND SLOTS by Baronius

Converting BG1 items to BG2

crossing the great divide: Coding for BG1, Tutu and BGT

Extended list of BG1 character sound offset names, BG1 TotSC by Baronius

IESDP

IWD engine: dialogue coding

Near Infinity

tp2-patching for cross-coding BG1 and BGII, described by Miloch

Zed Nocear's Trigger-Simulations for BG1
« Last Edit: August 01, 2008, 06:15:24 AM by jastey »

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #1 on: April 06, 2008, 05:30:39 AM »
1. General

BAMs: only decompressed. BGII BAMs are usually compressed. For BG1, they have to be decompressed, or the item will lead to a CTD. Decompressed BAMs work in BGII. Near Infinity can be used to decompress BAMs.
Further, description BAMs have to be quartered in BG1 into four frames. BGII description BAMs are unsplit, they have to be split if meant to be used in BG1. I used BAMWorkshop II (scroll down the page) to split the BGII BAMs. Note: In BG1:TotSC, unsplit BAMs work as intended. (Split BAMs also work fine in BGII.)

There is no overall script like "Baldur.BCS" in BG1. A replacement could be the dplayer3.bcs, which is the script of Player1. Some "monologues" are triggered via this script in the game.

cre and itm, if they are supposed to work in both BG1 and BGII engine (for Tutu or BGT): BG1 items and creature files without any special abilities, effects or weapon proficiencies will work for BGII, but not necessarily the other way around. Therefore, if you are planning a mod that should be compatible with all BG1 games, I suggest creating the files using BG1 resources.
For differences in special abilities, effects or weapon proficiencies, please have a look at Miloch's (aka Tervadh) tutorial Converting BG1 items to BG2: Weapon proficiencies need to be set (at 0x31), otherwise all items will get a Large Sword proficiency.  All the kit usability flags need to be unset as relevant.  Since BG1 did not have kits, it didn't matter whether they were set or not. I could imagine differences could be covered during installation via tp2-patching depending on the detected game, as described by Miloch, but I cannot provide examples for this yet.

No dialogues at rest
NPCs have no dream script. There is no "...D.bcs", meaning that the engine is not capable of directly triggering a dialogue before rest, i.e. if the player presses the "rest" button.
I know of no way of realising this for BG1.
For the Ajantis vanilla BG1 romance, I used the following workaround: upon entering a tavern, the NPC starts a dialogue in which the player can choose whether he wants to directly end and therefore postpone the dialogue, or whether he wants to play the dialogue that will be followed by a forced rest. The dialogue is triggered by a variable that got set in the script of the tavern area.

Example:
The dialogue would start like this:
Code: [Select]
IF WEIGHT #-2 ~Global("X#AjantisReadyForNT1","GLOBAL",3)~ THEN resttalk_1
SAY ~Is this where we will rest for today?~
++ ~No, not yet.~ DO ~SetGlobal("X#AjantisReadyForNT1","GLOBAL",4)~ + resttalk_1_0a //-> ends the dialogue and resets the timer for the next tavern
++ ~Yes, we will rest immediately.~ DO ~SetGlobal("X#AjantisReadyForNT1","GLOBAL",4)~ + resttalk_1_0b //-> plays the dialogue, followed by a forced rest
END
In the tavern script, the variable "X#AjantisReadyForNT1" was set to 1 beforehand, and in ajantis.bcs, the dialogue was triggered.

tp2-Patching of the tavern scripts looks like this (only the first levels of taverns were used, as the dialogue gets triggered after the entering of the tavern. Thanks to Zed Nocear for providing the list):
Code: [Select]
//Patching of the tavern scripts for "rest talk"
EXTEND_BOTTOM ~AR1001.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0116.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0119.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0103.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0114.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0705.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0105.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR0133.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR2301.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR3304.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR3307.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR3351.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~
EXTEND_BOTTOM ~AR3357.BCS~ ~C#AjantisRomance_BG1/Scripts/C#Tavern_AD.BAF~

"Greeting sound": In BG1, it is not possible to "mute" a creature by assigning another blank sound reference (e.g. [C#BLANK]). Creatures say their assigned "greeting sound" nontheless. This results from a difference in processing of the sound reference between the engines, but I don't know how exactly. I haven't tried to assign a different sound file to a creature, so I don't know whether both sounds would be played if the creature is talked to, but considering Kulyok's experience for IWD I assume they'd do.

Coding interjections is a bit more complicated, since INTERJECT_COPY_TRANS (and presumably INTERJECT, as well) doesn't work as intended: The PC replies of the original dialogue state show during the interjection lines.
To simulate I_C_T, as it would appear using the BG2 engine, the following workaround has to be used ((thanks to Kulyok). Please refer also to the tutorial IWD engine: dialogue coding):

You need three files, that get compiled separately. The first one is a placeholder and is called PREFIX_DUMP.d. Make as much entries as you have interjections, for example:
Code: [Select]
BEGIN PREFIX_DUMP
IF ~~ 0   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 1   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 2   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 3   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 4   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 5   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 6   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 7   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 8   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 9   SAY ~Temp~ IF ~~ THEN EXIT END
IF ~~ 10  SAY ~Temp~ IF ~~ THEN EXIT END

The second file is called REPLACE.d. It is for saving the original, and adding an additional transition trigger to the original transitions of the state where the interjection should be added. The third file is the actual interjection, divided into an EXTEND_BOTTOM that adds the new interjection to the original state, and a CHAIN that allows the interjection flow like an I_C_T type of interjections. Important is to add a check variable, so the interjection only runs once. This variable can also be used as new transition trigger for the original transitions, so they show the next time once the interjection occurred since then check variable = 1.

The only drawback, and I guess there is no solution to that, is the missing OR() trigger in BG1. This makes it necessary to add a stupid extra-line to the original state, in case the NPC isn't able to speak his interjection line. A way to avoid this is to use less triggers for the interjection, e.g. only "InParty("npc")" instead of the triple "InParty("npc") See("npc") !StateCheck("npc",CD_STATENOTVALID)", but I wouldn't advise to.

Example. Ajantis meets Raleo Windspear in Beregost, and greets him as the brother of Lord Garren Windspear. So we have (please note that "C#" is my personal prefix):

PREFIX_DUMP.d:
Code: [Select]
BEGIN PREFIX_DUMP
IF ~~ 0
SAY ~TEMP~
IF ~~ THEN EXIT
END

REPLACE.d:
Code: [Select]
REPLACE PREFIX_DUMP
IF ~~ THEN 0
SAY ~TEMP~
COPY_TRANS Raleo 0
END
END

ADD_TRANS_TRIGGER Raleo 0 ~Global("C#AjantisBG1RALEO0","GLOBAL",1)~ //original transitions fire if the interjection run

INTERJECTION.d:
Code: [Select]
EXTEND_BOTTOM RALEO 0
IF ~Global("C#AjantisBG1RALEO0","GLOBAL",0)~ EXTERN RALEO Ajantis_Windspear_interjection
IF ~InParty("ajantis") Detect("ajantis") !StateCheck("ajantis",CD_STATE_NOTVALID) Global("C#AjantisBG1RALEO0","GLOBAL",0)~ EXTERN AJANTJ Ajantis_Windspear_interjection
END

CHAIN AJANTJ Ajantis_Windspear_interjection
~Sir Raleo Windspear! I am delighted to see (blabla).~ DO ~SetGlobal("C#AjantisBG1RALEO0","GLOBAL",1)~
== RALEO ~Ah, the youngest Ilvastarr's offspring. (bla).~
== RALEO ~Indeed. That will be another while, though. But now I am sure your friend has some questions about this town, hm?~
END
COPY_TRANS PREFIX_DUMP 0

CHAIN RALEO Ajantis_Windspear_interjection
~Don't be shy and ask away.~ //stupid passback line if Ajantis didn't come to say his bit
END
COPY_TRANS PREFIX_DUMP 0

All that's missing is the lines in the tp2, to compile the files separately:
Code: [Select]
COMPILE ~Mod/Dialogue/PREFIX_DUMP.d~
COMPILE ~Mod/Dialogue/REPLACE.d~
COMPILE ~Mod/Dialogue/INTERJECTION.d~

Items with charges: there exists no "recharges after rest" behaviour. This behaviour could be simulated by replacing the old item with a fresh one after rest, using Zed Nocear's Trigger-Simulations for BG1, but for BG1, it's only limited charges or always usable.

Add mod NPC to BG1 Joinable NPCs have to be listed in the baldur.gam. In BGII every NPC that is not listed in Baldur.gam will be added automatically as soon as he joins the group, hence in BGII, the baldur.gam doesn't have to be altered by a mod. The BG1-Engine does not add NPCs automatically to the baldur.gam, but the mod has to alter the baldur.gam to make the mod NPC fully available in the game. If not, the game will crash the first time the NPC tries to travel fom one area to another.
WeiDU offers ADD_GAM_NPC (please refer to the WeiDU-readme), but with ADD_GAM_NPC all BALDUR.GAM files in SAVE and MPSAVE get altered, which is... cruel. EDIT: It seems that this leads to scew-ups if a mod that adds NPCs via ADD_GAM_NPC gets installed twice, as it can happen easily during a complex install, plus a proper deinstallation doesn't seem to be possible: ADD_GAM_NPC and uninstallation, non-functionality of.
Zed Nocear suggests the following possibility to add a custom joinable NPC to the game via the tp2:
Code: [Select]
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_ASCII 0xC0 ~MYNPC~  // name of file MYNPC.CRE (without extension)
  WRITE_ASCII 0xCC ~AR2600~ // area that you want NPC placed in
  WRITE_SHORT 0xD4 123 // x co-ordinate on the map
  WRITE_SHORT 0xD6 456 // y co-ordinate on the map
  WRITE_LONG 0xC8 7   // face orientation

This solution has one disadvantage: A new game has to be started for the NPC to be present in the game.
The advantages are:
- only the main file Baldur.GAM gets changed, SAVEs don't get touched.
- the NPC-CRE-changes as defined by NPCLEVEL.2DA get applied, depending on the level of the PC (this would also be true for ADD_GAM_NPC).
- "face orientation" can be used.
- ADD_GAM_NPC carries the flag "do not use this".

Post-dialogue of joinable NPCs
In BG1, the engine does not switch automatically from the joined dialogue "...J.dlg" to the post dialogue "...P.dlg" upon kickout. The kickout dialogue state is contained in the joined dialogue. Upon kickout, the post-dialogue has to be set via ~SetDialog("mynpcP")~. Upon joining, the dialogue is triggered via the post dialogue and the dialogue file is changed to the joined dialogue like in BGII.
An example: Ajantis' kickout dialogue is in his AJANTJ.dlg:
Code: [Select]
IF ~True()
~ THEN BEGIN 1 // from:
  SAY #16623 /* ~Aber ... aber ... wir waren doch ein Team und durch Ehre verbunden! Seufz... Warum ist bloß alles immer so kompliziert?
~ */
  IF ~~ THEN DO ~LeaveParty()
SetDialog("AjantP")
~ EXIT
END

This means in addition, that no "Kickout" variable is needed in BG1.

Further tutorials connected to NPC creation: Extended list of BG1 character sound offset names, BG1 TotSC by Baronius, linking to the BG1 CREATURE SOUND SLOTS by Baronius

Player initiated dialogue  In BGII, player initiated dialogue (PID for short) can be realized by selection of the "dialogue" button, and klicking on the NPC. In BG1, chosing the "dialogue" icon will not work if applied to party members. To realize PIDs in BG1, I therefore chose the hotkey to enable the dialogue. The hotkey will only be executed, if the NPC in question is selected. Please note: If more than one NPC is selected, all could react to the hotkey. If all of them have PIDs that are coded like this, they would fire their dialogue one after the other.
I used the hotkey "k", and (instead of having the last dialogue state triggered with ~True()~ to be the PID like in BGII) I used a variable that will be set, and reset to "0" by the script. Further, the script block activated by the hotkey will lead to the NPC approaching to the PC, and only if he sees her. This was to make up for the missing "IsGabber()", to make sure it is the NPC and the PC talking.
So, to enable the hotkey, a script block has to be added to the NPC's script. The second block is for setting the variable back to zero, this will happen after the flirt menu was opened. (Theoretically, this variable could be set to zero in the dialog file after the flirt. But I didn't feel like adding closing variables for x++ flirts, and forgetting some in the end, so I went for the easier solution.):
Code: [Select]
// Player-initiated dialogue
IF
InParty(Myself)
HotKey(K)
Detect(Player1)
!StateCheck(Myself,CD_STATE_NOTVALID)
!StateCheck(Player1,CD_STATE_NOTVALID)
THEN
RESPONSE #100
SetGlobal("C#AjantisPCIniFlirtTime","GLOBAL",1)
Dialogue(Player1)
END

IF
Global("C#AjantisPCIniFlirtTime","GLOBAL",1)
THEN
RESPONSE #100
SetGlobal("C#AjantisPCIniFlirtTime","GLOBAL",0)
END
And in the dialogue file, the PID dialogue state trigger would look like this:
Code: [Select]
IF WEIGHT #-2
~Global("C#AjantisPCIniFlirtTime","GLOBAL",1) Global("X#AjantisRomanceMatch","GLOBAL",1) Global("X#AjantisRomanceActive","GLOBAL",1) !Detect([ENEMY])~ THEN BEGIN ajantisflirts_begin_01
SAY @0
...

Portraits The used format notation is "S" for the small and "L" for the medium sized portraits. Large, like they are used in BGII for the ToB epilogue, are not used in BG1.

No real time timers
RealGlobalTimerExpired() and RealSetGlobalTimer() do not exist: there is no real time timer. Only the global game time timer (GlobalTimerExpired() - SetGlobalTimer()) exists. Possible time frames are ONE_DAY, TWO_DAYS etc.
If using numbers, like "150", experiences vary. Some say, it leads to unstable results. Zed Nocear reported the following:
From GTIMES.IDS, a game day takes 7200 seconds. The timer counts in real seconds, if the game is set to 30 frames per second. This would mean, that "150" is two minutes real time, or half an hour game time.

Variables: Original BG1 uses mostly global variables. BG1:TotSC uses two AREA-type variables. Still, all three variable types exist ("GLOBAL", "LOCALS", "AREA"), but with some difference in how they function compared to BGII (by Zed Nocear):

GLOBAL-variables function the way they do in BGII.

LOCALS-variables work like in BGII, but they are not saved in the savegames. This means, that after every start or loading of the game the local variables are reset to zero. They are like temporary help variables. This might be of advantage, as they do not accumulate in the SAVEs.
Further, there seems to exist an upper boundary as to how much local variables can be used, although I don't know whether for the whole game or per creature. Trying to set 80 LOCAL variables via dplayer3.bcs has led to a CTD.

AREA-variables work, but they are globally available like GLOBAL variables. "MYAREA" is not available in BG1, so for AREA-variables, the area name has to be specified, which makes the variable available in other areas, too.
For testing, the NPCs of the party were placed in different inside areas. If a variable "Global("VariableName","AR0701",1)" was set of one creature in AR0701, it could be read by the script of an NPC in area AR0702.
Therefore, AREA-variables seem to be useless and can be replaced by global variables.

Setting of variables in dialogues and scripts - how fast are the new values available? (by Zed Nocear) If a variable gets set in a dialogue or a script, how long does it take until it is available? It will lead to errors, if a variable is called before the engine actually got to setting the new value.

1. Script (this behaviour is equal to BGII, as far as I know, but I will list it nonetheless): A script gets executed from top to bottom, until a block with valid trigger is found. This block gets executed, and the script execution starts from the top again. If the script block was about setting a new variable value, the new value will be available after the new script call.
An exception to the restart of the script appears if Continue() is used at the end of the script block. In this case, the following script block will be executed directly, but all variables will still have the old value. The new values, again, will only be available after the restart of the script.

2. dialogue: Experience has shown, that for variables set with SetGlobal(), the new value was available in the following dialogue state. For IncrementGlobal(), it takes two dialogue states, though.
« Last Edit: August 01, 2008, 06:16:58 AM by jastey »

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #2 on: April 06, 2008, 05:31:04 AM »
2. Triggers

Alignment triggers: MASK_GOOD, MASK_EVIL and MASK_NEUTRAL do not seem to work for BG1 at all. Further, the single "alignmen.ids" identifiers ("LAWFUL_GOOD","CHAOTIC_NEUTRAL", etc.) if used like this don't seem to work, either. For checking the alignment of a creature, the (correct) numbers have to be used:

0 NONE
17 LAWFUL_GOOD
18 LAWFUL_NEUTRAL
19 LAWFUL_EVIL
33 NEUTRAL_GOOD
34 NEUTRAL
35 NEUTRAL_EVIL
49 CHAOTIC_GOOD
50 CHAOTIC_NEUTRAL
51 CHAOTIC_EVIL

Thus, to check for a CHAOTIC_GOOD PC, the trigger would be "Alignment(Player1,49)".

AreaCheck() does not exist. Have a look at Zed Nocear's Trigger-Simulations for BG1 for a neat workaround for this.

AreaType() does not exist. Have a look at Zed Nocear's Trigger-Simulations for BG1 for a neat workaround for this.

Class check: class.ids does not contain the _ALL identifiers ("MAGE_ALL" etc.) known from BGII. For BG1, only the plain class identifiers are available (for example "MAGE").

CombatCounter(0) does not exist. Have a look at Zed Nocear's Trigger-Simulations for BG1 for a possible workaround.

IsGabber(O:Object*) does not exist.

Level(), including LevelGT() and LevelLT() do not exist. "CheckStatLT(Myself,10,LEVEL)" might work, but I have no experience with this. As avenger_RR noted: "Note however, that LEVEL, LEVEL2 and LEVEL3 are different values and are used for multi and dual-classed characters".

OR() does not exist.
To minimize the amount of necessary script blocks for a large amount of "OR()" triggers, there are workarounds possible. See tutorial of Vlasak for coding helps concerning OR() and AND(): AND and OR in scripting, How to simulate them in various IE games
From my experience, I want to add: Always keep in mind, that scripts and dialogue files are executed from top to bottom. Take the following example, two dialogue blocks from Ajantis' player initiated dialogues. According to the alignments of the party members, different dialogues should be triggered:
Code: [Select]
//Original dialogue blocks as used in Tutu/BGT
IF //first block: no party member is of evil alignment
~Global("C#AjantisPCIniFlirtTime","GLOBAL",1) Global("X#AjantisRomanceMatch","GLOBAL",1) Global("X#AjantisRomanceActive","GLOBAL",1) !Detect([ENEMY])
!TimeOfDay(Night)
!Alignment(Player1,MASK_EVIL)
!Alignment(Player2,MASK_EVIL)
!Alignment(Player3,MASK_EVIL)
!Alignment(Player4,MASK_EVIL)
!Alignment(Player5,MASK_EVIL)
!Alignment(Player6,MASK_EVIL)
GlobalLT("X#AjantisRomanceBadDecision","GLOBAL",5)~ THEN BEGIN ajantisflirts_begin_01
SAY ~...~

IF //second block: at least one NPC is of evil alignment
~Global("C#AjantisPCIniFlirtTime","GLOBAL",1) Global("X#AjantisRomanceMatch","GLOBAL",1) Global("X#AjantisRomanceActive","GLOBAL",1) !Detect([ENEMY])
!TimeOfDay(Night)
!Alignment(Player1,MASK_EVIL)
OR(5)
Alignment(Player2,MASK_EVIL)
Alignment(Player3,MASK_EVIL)
Alignment(Player4,MASK_EVIL)
Alignment(Player5,MASK_EVIL)
Alignment(Player6,MASK_EVIL)
GlobalLT("X#AjantisRomanceBadDecision","GLOBAL",5)~ THEN BEGIN ajantisflirts_begin_02
SAY ~...~

First the MASK_EVIL has to be replaced as described above. Then, Instead of replacing the OR() in the second dialogue block in the example with a lot of new blocks, putting it last as unconditioned is equal to the wanted trigger condition, since the dialogue file will be executed from top to bottom, therefore the second block only will be triggered if the conditions of the first aren't met.
Code: [Select]
//Adapted dialogue blocks for BG1:
IF //first block: no party member is of evil alignment, BG1 syntax
~Global("C#AjantisPCIniFlirtTime","GLOBAL",1) Global("X#AjantisRomanceMatch","GLOBAL",1) Global("X#AjantisRomanceActive","GLOBAL",1) !Detect([ENEMY])
!TimeOfDay(Night)
!Alignment(Player1,19)
!Alignment(Player1,35)
!Alignment(Player1,51)
!Alignment(Player2,19)
!Alignment(Player2,35)
!Alignment(Player2,51)
!Alignment(Player3,19)
!Alignment(Player3,35)
!Alignment(Player3,51)
!Alignment(Player4,19)
!Alignment(Player4,35)
!Alignment(Player4,51)
!Alignment(Player5,19)
!Alignment(Player5,35)
!Alignment(Player5,51)
!Alignment(Player6,19)
!Alignment(Player6,35)
!Alignment(Player6,51)
GlobalLT("X#AjantisRomanceBadDecision","GLOBAL",5)~ THEN BEGIN ajantisflirts_begin_01
SAY ~...~

//second block, only triggers if one group member except player1 is of evil alignment
IF WEIGHT #-2
~Global("C#AjantisPCIniFlirtTime","GLOBAL",1) Global("X#AjantisRomanceMatch","GLOBAL",1) Global("X#AjantisRomanceActive","GLOBAL",1) !Detect([ENEMY])
!TimeOfDay(Night)
!Alignment(Player1,19)
!Alignment(Player1,35)
!Alignment(Player1,51)
GlobalLT("X#AjantisRomanceBadDecision","GLOBAL",5)~ THEN BEGIN ajantisflirts_begin_02
SAY ~...~

PartyRested() does not exist. To trigger a dialogue directly after rest, have a look at Zed Nocear's Trigger-Simulations for BG1 for a workaround for this.

XP(), including XPGT() and XPLT() do not exist. For checking the amount of XP an NPC has, a check of the form "CheckStatLT(Myself,161000,XP)" can be used instead.
« Last Edit: April 07, 2008, 03:36:09 PM by jastey »

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #3 on: April 06, 2008, 05:31:24 AM »
3. Actions

Here, some difference between BG1 and BG1:TotSC have to be considered. They are pointed out accordingly.

ApplySpellRES() works for both BG1 and BG1:TotSC (by Zed Nocear).
The Action.ids has to be patched accordingly:

APPEND ~action.ids~ ~160 ApplySpellRES(S:ResRef*,O:Target*)~       
UNLESS ~ApplySpellRES~

CreateCreature() For BG1:TotSC, the syntax for CrateCreature() is the same as in BGII: "CreateCreature("name",[x.y],0)", with "0" being the face orientation of the creature.
For BG1 without TotSC, there is no face orientation: "CreateCreature("name",[x.y])" is the correct syntax here.
To code for both in one file, the method described by cmorgan's "Crossing the Great Divide" tutorial using OUTER_SPRINT has to be used (see here for more details: crossing the great divide: Coding for BG1, Tutu and BGT).

CreateCreatureObjectOffScreen() does not exist. This action is used in BGII to create creatures outside the visual range of the group, but in BG1, this action is not available.

CreateItem() doesn't work for containers: The item lands on the ground in front of the container.
Normally, creating an item in a container would be done via area script, for example:
IF
   Global("C#ItemInChest","GLOBAL",0)
THEN
   RESPONSE #100
         SetGlobal("C#ItemInChest","GLOBAL",1)
         ActionOverride("Container1",CreateItem("C#ITEM",0,0,0))
END
Unfortunately, this does not work for BG1: the item is not created inside the container, but in front on the ground.

CreateVisualEffectObject() does not exist.

EraseJournalEntry() is not available/ does not exist. To cross-code for Tutu/BGT and BG1, and make the EraseJournalEntry() available to the Tutu/BGT versions, see here for more details: crossing the great divide: Coding for BG1, Tutu and BGTl.

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.

FaceObject() does not exist.
If you know where the two characters that should face each other are standing (either they were created or moved to another area with known coordinates) "Face()" could be used to let them turn.
If you don't know where the chracters are going to be, devSin proposed a possible workaround which I haven't checked yet, though: "You could always do something (...) like have your NPC ReallyForceSpell(Player1,FAKE_SPELL_DOES_NOTHING) Wait(1) StartDialog("DLG",Player1). Spellcasting should have the character zip around to face the target (you have to create the fake spell, though)."

FindTraps() does not work. Instead, it seems to disables the "find trap" modus, if it was chosen by the player by clicking on the game icon. If used in scripts, the thief would be unable to use the "find traps" modus as it gets repeatedly disabled. (by Zed Nocear)

GivePartyGoldGlobal(S:Name*,S:Area*) does not work as intended. IESDP: "Will give the party a sum of gold corresponding to the given global variable. The gold amount is deducted from the actor running the script." In BG1, the amount specified by the value of the global variable will be ignored, and all gold of the creature running the script will be transferred. (by Zed Nocear)

Journal entries:
only JOURNAL. There exists no "SOLVED_JOURNAL" or "UNSOLVED_JOURNAL" as known from the BGII engine (To cross-code for Tutu/BGT and BG1, see here for more details: crossing the great divide: Coding for BG1, Tutu and BGT)

Journal entries as actions: AddJournalEntry(~text~). For BGII engine, the syntax is AddJournalEntry(~text~,QUEST_DONE) for solved journal, and AddJournalEntry(~text~,QUEST) for unsolved quests. (To cross-code for Tutu/BGT and BG1, see here for more details: crossing the great divide: Coding for BG1, Tutu and BGT)

107 MoveToOffset(P:Offset*) does not work in BG1 and BG1:TotSC. If used, nothing will happen.

RestParty() does not exist.
I use the following Workaround:
1a. In a tavern, for BG1:TotSC:
A cutscene is used, that blackens the screen for a moment (there is no "rest at inn" movie in BG1, so in this case blackening the screen would be sufficient):
Code: [Select]
ClearAllActions()
StartCutSceneMode()
CutSceneId(Player1)
FadeToColor([20.0],0)
Wait(5)
Rest()
ActionOverride(Player2,Rest())
ActionOverride(Player3,Rest())
ActionOverride(Player4,Rest())
ActionOverride(Player5,Rest())
ActionOverride(Player6,Rest())
Wait(10)
FadeFromColor([20.0],0)
Wait(1)
EndCutSceneMode()
1b. In a tavern, BG1 without TotSC: For BG1 without TotSC, FadeToColor() is not available. I helped myself by triggering the "resting outdoor" movie also for an inside rest. See 2. for the cutscene.

2. For resting outdoor, the "resting outdoor" movie can be triggered (BG1 with or without TotSC):
Code: [Select]
ClearAllActions()
StartCutSceneMode()
CutSceneId(Player1)
Rest()
ActionOverride(Player2,Rest())
ActionOverride(Player3,Rest())
ActionOverride(Player4,Rest())
ActionOverride(Player5,Rest())
ActionOverride(Player6,Rest())
StartMovie("REST")
Wait(1)
EndCutSceneMode()

SetPlayerSound() does not exist. I don't know any workaround, so this means that there is no possibility to provide a "fix-it" script to restore the creature's sounds in BG1.

StartDialogNoSet() does not exist. Dialogue() has to be used instead.

TakePartyItem(S:Item*) and
TakePartyItemNum(S:ResRef*,I:Num*) should both be used with care: Stackable items in the inventory are treated as one and all are items of the stack are removed. (by Zed Nocear)
« Last Edit: August 01, 2008, 06:17:25 AM by jastey »

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #4 on: April 06, 2008, 05:31:46 AM »
4. Spell Effects

#122 (0x7a) Item: Create Inventory Item [122] does not work as in BGII: It will always only create one item, and only, if timing mode is set to "Delay/Permanent (4)" and number of items is set to "0". (by Zed Nocear)

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #5 on: April 06, 2008, 05:32:04 AM »
5. Tokens

Not all BGII Tokens are available.

I repost the list from the IESDP for BG1:TotSC here:

<CHARNAME>     Returns the name of the PC.
<DAY>    Returns the current numerical day.
<DAYANDMONTH>    Returns the current numerical day as well as the month. (Example: It is <DAYANDMONTH>, would produce: It is 24 Mirtul...or whatever the current day and month happens to be.)
<DURATION>    Returns the elapsed time from the start of the game in days and hours. (Example: We've been around for <DURATION>, would produce: We've been around for 23 days and 13 hours...or whatever the elapsed time happens to be.)
<DURATIONNOAND>    Returns the same thing as <DURATION> except it omits the and. So it would be 23 days 13 hours rather than 23 days and 13 hours.
<GABBER>    Returns the name of the current speaker. (Example: If I use Jaheira and click-talk her on a creature rather than using the PC, this would return Jaheira if used in a dialogue.)
<GAMEDAY>    Returns the current game day. (Starts at 1 for a new game.)
<GAMEDAYS>    Returns the number of game days that have elapsed since the start of the game. (Starts at 0 for a new game.)
<HOUR>    Returns the current hour of the day in numerical 24 hour format.
<MINUTE>    Returns the current number of real-time minutes (0-59) that have passed in the last hour.
<MONTHNAME>    Returns the current month's name. (Example: It is <MONTHNAME>, would produce: It is Mirtul...or whatever the current month is in your game.)
<YEAR>    Returns the current year in numerical format. (Example: It is <YEAR> currently, would produce: It is 1369 currently...or whatever year it is in your game.

I am not sure whether all tokens will work for BG1 without TotSC, but the tokens <LADYLORD> and <PRO_LADYLORD> definitely do not work for BG1 but return empty spaces.

Offline cmorgan

  • Planewalker
  • *****
  • Posts: 1424
  • Gender: Male
  • Searcher of Bugs
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #6 on: April 06, 2008, 10:40:50 AM »
Thank you!! (It also clears up some things about Tutu conversion).

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #7 on: April 07, 2008, 03:39:54 PM »
Thank you!
Tutorial is updated concerning quartering description BAMs in BG1:TotSC (where it is not necessary, only in plain BG1).

In addition, Link to Zed Nocear's Trigger-Simulations for BG1 added (I'd rather post this only at one place for now, in case it undergoes changes in near future).
« Last Edit: April 07, 2008, 03:43:07 PM by jastey »

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #8 on: August 01, 2008, 06:18:34 AM »
Updated.. since I seem to have forgotten this tutorial on the last update. :-[

Offline Miloch

  • Barbarian
  • Planewalker
  • *****
  • Posts: 1032
  • Gender: Male
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #9 on: August 01, 2008, 12:15:57 PM »
Updated.. since I seem to have forgotten this tutorial on the last update. :-[
Since I'm lazy, and don't want to read it all again, can you summarise what you updated?

Offline jastey

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 1524
  • Gender: Female
Re: BG1 Coding Tutorial - Differences to the BGII Engine
« Reply #10 on: August 01, 2008, 02:22:17 PM »
Sorry, should have done so.
Tutorial is updated concerning 107 MoveToOffset(P:Offset*) and ADD_GAM_NPC.

 

With Quick-Reply you can write a post when viewing a topic without loading a new page. You can still use bulletin board code and smileys as you would in a normal post.

Warning: this topic has not been posted in for at least 120 days.
Unless you're sure you want to reply, please consider starting a new topic.

Name: Email:
Verification:
Type the letters shown in the picture
Listen to the letters / Request another image
Type the letters shown in the picture:
What color is grass?:
What is the seventh word in this sentence?:
What is five minus two (use the full word)?: