forked from misterakko/sword-dream
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDreamMonsters.p
3701 lines (3471 loc) · 117 KB
/
DreamMonsters.p
1
Unit DreamMonsters;(*CONTIENEIl motore per combattimenti (FightingSystem)La dichiarazione del tipo TMostro e i suoi metodiL'intelligenza dei mostriLa gestione degli incontri con wandering monstersLa risoluzione degli incantesimi (lo SpellSystem)Il codice che permette di trovare un bersaglio (il TargetSystem)*)(*Regole:La arena nella quale avviene il combattimento quella specificata nellarisorsa encounter. Se l'incontro con wandering monster, viene sceltal'arena con lo stesso id del place dove ci trovavamo.Il morale dei mostri viene governato dalla regola seguente:il valore base specificato nel template del mostro. Viene decrementato di 1per ogni punto vita perso dal mostro, e incrementato di 1 per ogni punto feritainflitto dal mostro. Viene decrementato di 10 per ogni compagno che perde la vita,di 5 per ogni compagno che fugge dal campo di battaglia,incrementato di 15 per ogni PC che perde la vita,incrementato di 5 per ogni PC che fugge.All'inizio del combattimento viene modificato di (numPC - numMostri) * 2.A ogni round viene tirato un dado 100. Se il risultato superiore al morale delmostro, il mostro decide di fuggire.Se nel template del mostro indicato morale 255, il mostro non fugge mai.Caratteristiche correntemente non supportate in Dream (anche se servirebbero):¥ Stato berserk¥ Breath weapon¥ Teleport¥ Charm/summon e altre forme di "lui combatte con me"*)INTERFACEUses Types, QuickDraw, { List 2 } Controls, Events, OSUtils, SegLoad, { List 3 - needs List 1/2 types } Files, { needs OSUtils, SegLoad } TextEdit, { needs Events, Controls } Windows, { needs Events, Controls } { List 4 - needs List 1/2/3 types } Dialogs, { needs TextEdit, Windows } Lists, ObjIntf, Lista3, BinIO, TaskMaster3, DreamTypes, LowLevel, HiLevel;TYPE TMostro = OBJECT (TCreatura) mySoundID: Integer; { Per ruggire } XP: Longint; { New for v2.1 } morale: Integer; memory: MonsterMemory; attackSelector, defenseSelector, mdelCounter: Integer; { attackSelector: SE il mostro ha Missile, attackSelector il suo raggio mdelCounter: Which Mdel action will be selected next } specialAbilities: BitsInWord; { [7] Can see invisible [6] Can be invisible [5] Flys [4] Regenerates [3] is undead [2] is Large ------ New for v2.0 } mdel: MDeLHandle; { Or NIL if embedded intelligence moves it } nextMonster: TMostro; PROCEDURE Draw (where: rect; how: INTEGER); OVERRIDE; PROCEDURE Init (ref: integer); OVERRIDE; FUNCTION Time (amountInMin: integer): boolean; OVERRIDE; FUNCTION Kill: boolean; OVERRIDE; FUNCTION Move: char; OVERRIDE; END;CONST rWindArena = 150;VAR numChars: INTEGER; { Numero personaggi che combattono (numPC - morti e inabili } { Questa sarebbe privata di FightingSystem, ma qui di modo che il codice chiamante possa scoprire se la fine dell'incontro si risolta perchŽ il gruppo ha vinto o perchŽ fuggito. In questo secondo caso, infatti, lo rimette dov'eraÉÊ} arenaWindow: WindowPtr; { Questa sarebbe riservata di FightingSystem e TargetingSystem, ma qui di modo che sia possibile salvarne la posizione } movingGuy: TCreatura;PROCEDURE MonstersInit;PROCEDURE MonstersShutdown;PROCEDURE DoWanderingMonsters (refID: INTEGER);{ Da chiamare quando appaiono wandering monsters }PROCEDURE DrawArena (myWin: WindowPtr);{ Procedura che disegna l'arena del fighting system. é qui di modo che sia pos-sibile usarla per stampare la finestra }PROCEDURE FightCoord2ArenaRect (x, y: INTEGER; VAR r: Rect);Procedure FightingSystem (ref: INTEGER; encounter: BOOLEAN; npcInvolved: Integer);{ Chiamato dall'Ambiente per eseguire un combattimento.Se encounter true, carica i gruppi di mostri specificatinella risorsa Encounter di riferimento indicato. Altrimenti carica un gruppodi mostri dall'identificativo di riferimento indicato, in numero di uno-max,dove max indicato nella risorsa caratteristica.Se NPCinvolved diverso da zero, il motore 3D viene pregato di ucciderlo incaso di fine vittoriosa dello scontro }PROCEDURE SpellSystem2;{ Implemementatore di incantesimi.Il fightingsystem deve intervenire se in uscita damage <> 0 }FUNCTION TargetSystem (fightTime: Boolean; maxTargetDistance: Integer): Boolean;{ Permette all'utente di scegliere un bersaglio. Dipende da attackAdditionalInfoper sapere che tipo di bersaglio pu˜ essere scelto, e mette in attackAdditionalInfoil risultato. Restituisce TRUE se la sequenza di attacco pu˜ procedere, FALSEse va abortita.Viene usato dallo spell code per trovare i bersagli, e dal FightingSystemper le armi da lancio.}IMPLEMENTATIONUSES Appearance, Icons, Memory, Menus, QDOffscreen, Resources, TextUtils, Cilindro, DialogLord4, MusicEngine, Engine3D_NPC, DreamIO, GraphEngine, DreamMagic, Characters;CONST ttInvisible = 4; { Per PlotCreature, si aggiunge ai consueti ttNone, ttOffline... }(* fighting system data *)VAR numMonsters: Integer; { Numero mostri in lista } primoMostro: TMostro; { Lista linkata dei mostri } mmm: Integer; { Monster morale modifier. Vedi discussione del morale sopra } arenaX, arenaY: Integer; arenaBounds: Rect; { Rect 1, 1, arenaX+1, arenaY+1 usato dal codice secondario } arenaWorld: GWorldPtr; { L'arena, senza i guerrieri } arena: ARRAY [1..maxLocX, 1..maxLocY] of BitsInByte; { Solo le specs dal template }(********************************************)(* *)(* Codice di Inizializzazione. *)(* *)(********************************************){$S UtilInit}PROCEDURE MonstersInit;BEGIN { Carica la finestra } arenaWindow := GetNewCWindow(rWindArena+ord(gHasThemes)*1000, NIL, WindowPtr(-1)); IF arenaWindow = NIL THEN DeathAlert (errMissingApplRes, ResError); TMNewWindow (arenaWindow, 0, kArenaWindowRefCon, { refCon } (3+2)*32, (3+2)*32, { data height, width } (maxLocY+2)*32, (maxLocX+2)*32, { Max height, width } (3+2)*32, (3+2)*32, { min height, width } 0, 0, 0, 0, 0, NIL, DrawArena, NIL); movingGuy := NILEND;PROCEDURE MonstersShutdown;BEGIN { Non fa nulla, perchŽ viene chiamata al cambio di scenario o savegame, e in quei casi non ha senso chiudere la finestra dell'arena }END;(********************************************)(* *)(* Codice di bassissima manovalanza *)(* usato sia da TargetSystem che da *)(* FightingSystem. *)(* *)(********************************************){$S LowLevel}FUNCTION PosToMonster (pos: point; VAR monster: TMostro): BOOLEAN;{ Data una posizione sulla mappa, scopre se vi si trova un mostro, e inquesto caso chi. Restituisce true se c' qualcuno, false altrimenti.ATTENZIONE: non ha molto senso chiamarlo se il chiamante una mostro che giˆ stato spostato nel punto desiderato. In tal caso, ovviamente, PosToMonster resti-tuirebbe sempre TRUEÉ } BEGIN monster := primoMostro; WHILE (monster <> NIL) & (longint(monster.whereAmI) <> longint (pos)) DO monster := monster.nextMonster; PosToMonster := (monster <> NIL)END;{$S Characters}FUNCTION PosToCharacter (pos: point; VAR guy: TPersonaggio): BOOLEAN;{ Data una posizione sulla mappa, scopre se vi si trova un PC, e inquesto caso chi. Restituisce true se c' qualcuno, false altrimenti.}VAR all: EntityRef;BEGIN FOR all := numPC-1 DOWNTO 0 DO IF longint(Mondo[all].whereAmI) = longint (pos) THEN BEGIN guy := Mondo[all]; PosToCharacter := TRUE; Exit (PosToCharacter) END; PosToCharacter := FALSEEND;{$S DefProcs}PROCEDURE FightCoord2ArenaRect (x, y: INTEGER; VAR r: Rect);{ Incapsula la trasformazione da coordinate logiche (l'angolo in alto a sinistra 1, 1) a coordinate sullo schermo (locali alla finestra).SE VIENE CAMBIATO PER RIDURRE IL BORDO cambiare anche il codice che fa latrasformazione inversa in HandleClick dentro game.p,e la chiamata a SizeWindow dentro LoadArena }BEGIN SetRect (r, BSL(x,5), BSL(y,5), BSL(x+1,5), BSL(y+1,5));END;{$S LowLevel}PROCEDURE ArenaPos2FightCoord (clickPos: Point; VAR coord: Point);{ Speculare di FightCoord2ArenaRect }BEGIN WITH coord DO BEGIN v := clickPos.v DIV 32; h := clickPos.h DIV 32 ENDEND;{$S LowLevel}FUNCTION DistanzaInMosse (da, a: Point): INTEGER;{ Trova la distanza dal punto dove mi trovo a un altro punto qualsiasi,espressa in numero di mosse che devo compiere per arrivarci }VAR dx: Point;BEGIN WITH dx DO BEGIN v := Abs (a.v - da.v); h := Abs (a.h - da.h); IF v < h THEN DistanzaInMosse := h ELSE DistanzaInMosse := v END; { with }END;(********************************************)(* *)(* Codice di parte dello SpellSystem *)(* che si trova qui perchŽ deve sapere *)(* come funziona il combattimento. *)(* *)(********************************************){$S Characters}FUNCTION TargetSystem (fightTime: Boolean; maxTargetDistance: Integer): Boolean;{ Permette all'utente di scegliere un bersaglio. }VAR correctTargetRegion, tempRgn: RgnHandle; result: Boolean; tempRect, tr2, tr3: Rect; s: Str255; all: EntityRef; everyMonster: TMostro; FUNCTION SolveTargeting: Boolean; { Cuore del TargetSystem. Lascia cliccare sul bersaglio e restituisce TRUE se il clic su bersaglio accettabile. In questo caso attackAdditionalInfo.pivot ha il click point in coord locali} CONST rTargetingCursor = 128; VAR uncorrectTargetRegion, currentRegion: RgnHandle; mousepos: Point; c: CCrsrHandle; endTargeting: longint; oldTMpriority, maxTicks: Integer; e: wmTaskRec; onCorrectTarget, correctTargetClicked: Boolean; BEGIN { Caso speciale } IF (attackAdditionalInfo.groundZero = Caster) OR (attackAdditionalInfo.groundZero = AllMonsters) THEN BEGIN { salva il raggio d'azione, che serve a Dispatchattack nel caso AllMonsters, in un campo di attackAdditionalInfo che in quest caso non serve } attackAdditionalInfo.areaWidth := attackAdditionalInfo.attackingSpell.range; SolveTargeting := TRUE; Exit (SolveTargeting) END; { Trova la regione "no-good" } uncorrectTargetRegion := NewRgn; CloseRgn (uncorrectTargetRegion); SetRectRgn(uncorrectTargetRegion,-2000,-2000,2000,2000); DiffRgn (uncorrectTargetRegion, correctTargetRegion, uncorrectTargetRegion); { Leggi dalle risorse il cursore di targeting } c := GetCCursor (rTargetingCursor); { Salva il setup di TaskMaster } oldTMpriority := GetPriority; { Trova una taskmask adatta } e.wmTaskMask := tmUpdate+ { Update windows } tmFindW+ { Call FindWindow } tmOpenNDA+ { May access apple menu } tmSysClick+ { May switch } tmDragW+ { May drag windows } tmZoom+ { May zoom windows } tmGrow+ { May grow windows} tmCRedraw+ { May redraw controls } tmInfo+ { Don't activate window on click on info bar } tmContentControls+ {May call FindControl } tmMultiClick+ { Handles multiple clicks } tmIdleEvents + { Passes idle events to controls } tmDoDiskMount+ { May format bad floppies } tmMultiFinder; { May pass back suspend & resume } ; { Inizializza il risultato } correctTargetClicked := FALSE; { Prendi l'ora attuale } endTargeting := TickCount + 600; { 10 secondi per il targeting } REPEAT { Sistema un cursore adatto } GetMouse (mousepos); onCorrectTarget := PtInRgn (mousePos, correctTargetRegion); IF onCorrectTarget THEN BEGIN { Sta puntando a un bersaglio accettabile } SetCCursor (c); currentRegion := correctTargetRegion END ELSE BEGIN { Sta puntando a un bersaglio inaccettabile } InitCursor; currentRegion := uncorrectTargetRegion END; { Trova quanti ticks possiamo attendere una decisione } maxTicks := endTargeting - TickCount; IF maxTicks < 0 THEN maxTicks := 0; TaskPriority (maxTicks); { Chiama Taskmaster e lasciagli scegliiere. TaskMaster, grazie a WaitNextEvent, mi da un evento quano esco dalla regione specificata, quindi tengo in currentregion una copia della handle a currect- o uncorrect- TargetRegion } CASE TaskMaster (everyEvent, currentRegion, e) OF { TN 205 } wInContentRgn: IF onCorrectTarget THEN BEGIN correctTargetClicked := TRUE; attackAdditionalInfo.Pivot := Point (e.wmTaskData2) END ELSE DoSoundAsync (sndImpossible); keyDown: IF chr(BAnd(evento.wmTaskData, charCodeMask)) = chEscape THEN { escape - abort the spell } maxTicks := 0; wSuspend: BEGIN KillSoundChannel; { Leave the sound hardware to other appls } gInBackground := true; CallMeAtSuspend; end; wResume: BEGIN { Ha switchato ad altra applicazione. Resetta endTargeting di modo che il tempo di gioco non sia meno dei 10 secondi stabiliti } endTargeting := maxTicks + TickCount; gInBackground := false; SetupSoundChannel; { Bug fix 1.6.1 } CallMeAtResume; END END; { case } UNTIL correctTargetClicked | (maxTicks = 0); { Liberati del cursore di targeting } InitCursor; DisposeCCursor (c); { Liberati della no-good region } DisposeRgn (uncorrectTargetRegion); { Resetta TaskMaster } TaskPriority (oldTMpriority); { Esci } SolveTargeting := correctTargetClicked; END;BEGIN { New for 1.3 - support independent monster decision logic } IF attackAdditionalInfo.targetSolved THEN BEGIN TargetSystem := TRUE; Exit (TargetSystem); END; correctTargetRegion := NewRgn; CloseRgn (correctTargetRegion); result := TRUE; { Salvo contrordini prosegui } IF fightTime THEN BEGIN { Porta in primo piano la finestra del combattimento } TMSelectWindow (arenaWindow); SetPort (arenaWindow); { Trova la regione dei bersagli } tempRgn := NewRgn; CloseRgn (tempRgn); WITH attackAdditionalInfo DO CASE groundZero OF Individual: BEGIN FOR all := numPC-1 DOWNTO 0 DO IF DistanzaInMosse (origin, Mondo[all].whereAmI) <= maxTargetDistance THEN BEGIN FightCoord2ArenaRect (Mondo[all].whereAmI.h, Mondo[all].whereAmI.v, tempRect); RectRgn (tempRgn, tempRect); UnionRgn (correctTargetRegion, tempRgn, correctTargetRegion) END; GetIndString(s, rUserIntfStrings, kTargetChar); END; { individual } OneMonster: BEGIN GetIndString(s, rUserIntfStrings, kTargetMonster); everyMonster := primoMostro; WHILE everyMonster <> NIL DO BEGIN IF DistanzaInMosse (origin, everyMonster.whereAmI) <= maxTargetDistance THEN BEGIN FightCoord2ArenaRect (everyMonster.whereAmI.h, everyMonster.whereAmI.v, tempRect); RectRgn (tempRgn, tempRect); UnionRgn (correctTargetRegion, tempRgn, correctTargetRegion) END; { if } everyMonster := everyMonster.nextMonster END { while } END; { caso OneMonster } RoundArea, SquareArea, StraightBolt: BEGIN GetIndString(s, rUserIntfStrings, kTargetArena); { Prendi nota che non c' un singolo bersaglio } target := NIL; targetRef := -1; { Espressa in coordinate dell'arena, la zona in cui l'incantesimo pu˜ venire lanciata l'intersezione di arenaBounds (tutta l'arena) e di un quadrato con al centro il caster e i lati distanti maxTargetDistance da lui. Iniziamo a calcolare quest'ultimo rettangolo. } WITH tempRect DO BEGIN top := origin.v - maxTargetDistance; bottom := origin.v + maxTargetDistance; left := origin.h - maxTargetDistance; right := origin.h + maxTargetDistance; END; { Troviamo l'intersezione } IF SectRect (tempRect, arenaBounds, tempRect) THEN BEGIN { Ora bisogna trasformare il rettangolo dal sistema di coordinate al sistema di pixel nel grafPort della arena window } FightCoord2ArenaRect (temprect.left, tempRect.top, tr2); FightCoord2ArenaRect (temprect.right, tempRect.bottom, tr3); tr2.botRight := tr3.botRight; { Ora tr2 temprect espresso in pixel } RectRgn (correctTargetRegion, tr2); END; { Intersezione calcolata } END; { Incantesimo su area } END; { case } { Emetti un messaggio applicabile } IF attackAdditionalInfo.attackingSpell <> NIL tHEN s := Concat (s, attackAdditionalInfo.attackingSpell.nome); StatusLine (s); DisposeRgn (tempRgn); IF SolveTargeting THEN WITH attackAdditionalInfo DO BEGIN { Restituisci in attackAdditionalInfo ulteriori informazioni, necessarie a DispatchAttack per risolvere gli incantesimi di area } areaWidth := attackingSpell.area; { Passa Pivot da coordinate della finestra a coordinate della mappa } ArenaPos2FightCoord (pivot, pivot); CASE groundZero OF Caster: { Target giˆ a posto (ci pensa CastSpell), TargetRef sconosciuto! } pivot := target.whereAmI; Individual: { Trova il personaggio che stato cliccato } IF PosToCharacter (pivot, TPersonaggio(target)) THEN ; OneMonster: BEGIN everyMonster := NIL; targetRef := -1; { Su che mostro dirigo il mio incantesimo? } IF PosToMonster (pivot, everyMonster) THEN target := everyMonster ELSE DeathAlert (0, -9999) END; OTHERWISE BEGIN { Incantesimi di area o di gruppo. Non c' target individuale } target := NIL; targetRef := -1; END { otherwise } END { case } END { If SolveTargeting found a target } ELSE { Didn't find a valid target } result := FALSE; END { if fighting time } ELSE { fighting system inattivo - sono chiamato da MainGameLoop } WITH attackAdditionalInfo DO IF groundZero = Individual THEN BEGIN { SpellCheck garantisce che se arrivo qui il bersaglio pu˜ solo essere Caster, Individual o AllGroup. Nel primo e nell'ultimo caso non ho nulla da fare. } { Porta in primo piano la main window } TMSelectWindow (mainWindow); SetPort (mainWindow); { Trova il rect dei bersagli } tempRect := groupRect; { é una globale } tempRect.bottom := tempRect.top + numPC * kIconHeight; { Bug fix 2.0.1 } { Trova la rgn dei bersagli } RectRgn (correctTargetRegion, tempRect); { Emetti un messaggio applicabile } GetIndString(s, rUserIntfStrings, kTargetChar); s := Concat (s, attackingSpell.nome); StatusLine (s); { Risolvi il targeting } IF SolveTargeting THEN BEGIN targetRef := (pivot.v - groupRect.top) DIV kIconHeight; target := Mondo[targetRef] END ELSE result := FALSE; END; { resetta la status line } StatusLine (''); { Libera la memoria allocata } DisposeRgn (correctTargetRegion); { Esci } TargetSystem := resultEND;{$S Magic}FUNCTION CoreSpellSystem: Boolean;{ Supremo Implementatore di Incantesimi! }VAR solved, dummy: Boolean; unNome: Str255; { Per il transcript } i, tgtLevel: Integer; loop: Storage; { Per Clone e Remove Curse } cicloMostro: TMostro; { Per detect invisibility 1.0 } attackKindAsEnum: KindOfAttack; okButton: Family; { per gli alert } statusLoop: OneStatus; FUNCTION Progressione (k: Integer): Integer; { Volevo una funzione che mi desse una progressione di percentuali per il fallimento della resurrezione. Ecco che cosa ho pensato: sottraggo a 18 la costituzione del personaggio. Ottengo un numero k compreso tra zero e 15. Calcolo 1+2+3+...+k. Quella la probabilitˆ percentuale che la resurrezione non abbia effetto. } VAR i, result: Integer; BEGIN result := 0; FOR i := 1 TO k DO result := result + i; Progressione := result END; PROCEDURE AssignProtection; VAR scanSpells: TIncantesimo; found: Boolean; BEGIN { Qs istruzione qui per evitare che il "target" campo di spell si confonda con il "target" campo di attackAdditionalInfo } scanSpells := attackAdditionalInfo.target.activeSpells; WITH attackAdditionalInfo.attackingSpell DO BEGIN { Per caso giˆ afflitto da questo spell? } found := FALSE; WHILE scanSpells <> NIL DO BEGIN IF (scanSpells.tipo = tipo) AND (scanSpells.codeSelector = codeSelector) THEN found := TRUE; scanSpells := scanSpells.nextSpell END; IF found THEN BEGIN { Se si, ritocca la durata } IF duratBase > scanSpells.duratBase THEN scanSpells.duratBase := duratBase END ELSE BEGIN { Se no, specifica che il nostro ora protetto } attackKindAsEnum := tipo; target.specialModifiers[attackKindAsEnum] := TRUE; END; CoreSpellSystem := found; { Se non trovato, assegnaglielo } Exit (CoreSpellSystem) END { with } END; { procedure } BEGIN ClearFamily (okButton); okButton[kStdOkItemIndex] := TRUE; solved := FALSE; WITH attackAdditionalInfo DO BEGIN { Per il transcript } IF target <> NIL THEN unNome := target.nome; { Incantesimi di protezione (caratterizzati da codeSelector -1) } IF attackingSpell.codeSelector < 0 THEN AssignProtection; CASE attackingSpell.tipo OF { 1. Incantesimo di cura. } Healing: BEGIN dummy := HPChange (target, damage); AddToTranscript (unNome, ktIsHealed, '', 0); damage := 0; solved := TRUE; END; { Healing } { 2. Incantesimi che fanno apparire oggetti, come ÒWizard SwordÓ: sono quelli di tipo WeaponXXX e Missile } Weapon, WeaponPlusOne, WeaponPlusTwo, WeaponPlusThree, WeaponPlusFour, WeaponPlusFive, Missile: IF target.status[IsPC] THEN BEGIN { Dagli l'oggetto } dummy := GiveItemToChar (TPersonaggio(target), ManoDx, attackingSpell.codeSelector, TRUE, TRUE); solved := TRUE { A farlo sparire pensano TimingSystem e TItem.Time } END; { case object creation } { 3. Gestione del veleno } Poison: CASE attackingSpell.codeSelector OF 1{ slow }, 2{cure}: BEGIN target.isPoisoned := 0; AddToTranscript (unNome, ktIsCured, '', 0); solved := (attackingSpell.codeSelector = 2) END; 3{ inflict }: BEGIN AddToTranscript (unNome, ktIsPoisoned, '', 0); solved := TRUE; target.isPoisoned := Succ (target.isPoisoned) END; END; { case } { 4. Gestione delle malattie } Illness: CASE attackingSpell.codeSelector OF 1{ slow }, 2{cure}: BEGIN target.status[IsIll] := FALSE; AddToTranscript (unNome, ktIsCured, '', 0); solved := (attackingSpell.codeSelector = 2) END; 3{ inflict }: BEGIN AddToTranscript (unNome, ktGetsIll, '', 0); solved := TRUE; target.status[IsIll] := TRUE; END; END; { case } { 5. Gestione di pietra-carne } TurnToStone: CASE attackingSpell.codeSelector OF 1{ slow }, 2{ cure }: IF target.status[IsStoned] THEN BEGIN target.status[IsDead] := FALSE; target.status[IsStoned] := FALSE; AddToTranscript (unNome, ktIsFleshed, '', 0); solved := (attackingSpell.codeSelector = 2) END; 3{ inflict }: BEGIN AddToTranscript (unNome, ktIsStoned, '', 0); solved := TRUE; target.status[IsDead] := TRUE; target.status[IsStoned] := TRUE; { Morale, setup posizione, etc? } END; END; { case } DeathMagic: BEGIN { bug fix 1.6 - mancavaÉ } solved := TRUE; damage := maxint END; { 6. Casi speciali - special cases } Special: CASE attackingSpell.codeSelector OF 1: { light } BEGIN artificialLight := succ (artificialLight); IF (artificialLight > 0) & placeData[6] { need light } THEN BEGIN { Lasciagli vedere i dintorni } SetPort (mainWindow); TMInvalRect (paneRect) END; solved := FALSE END; 2: { identify } BEGIN IF (target <> NIL) & target.status[IsPC] & (TPersonaggio(target).equipaggiamento[identifyWhat] <> NIL) THEN BEGIN TPersonaggio(target).equipaggiamento[identifyWhat].data[8] { known } := TRUE; ItemHasChanged (TPersonaggio(target).equipaggiamento[identifyWhat]) END ELSE SpellAlert (kTargetIsRightHand, 0); solved := TRUE END; 3: { slow } BEGIN target.attacchiPerDueRound := BSR (target.attacchiPerDueRound, 1); AddToTranscript (unNome, ktIsSlowed, '', 0); solved := FALSE END; { slow } 4: { haste } BEGIN target.attacchiPerDueRound := BSL (target.attacchiPerDueRound, 1); { Bug fix 2.1 } AddToTranscript (unNome, ktIsHasted, '', 0); IF target.status[IsPC] THEN TPersonaggio(target).eta := succ (TPersonaggio (target).eta); solved := FALSE END; { haste } 5: { feign death } IF NOT target.status[IsDead] THEN BEGIN KillAllSpells (target); target.status[IsDead] := TRUE; AddToTranscript (unNome, ktIsDead, '', 0); mmm := mmm + 15; numChars := pred (numChars); solved := FALSE END ELSE { Era giˆ morto?!? } solved := TRUE; 6: { hold monster } BEGIN { Memorizza all'interno dell'incantesimo le normali capacitˆ combattive dell'avversario } attackingSpell.reserved := target.attacchiPerDueRound; target.attacchiPerDueRound := 0; solved := FALSE { mantieni vivo l'incantesimo in attesa della fine } END; 7: { restore } IF target.status[IsPC] & (TPersonaggio (target).maxXP > TPersonaggio (target).XP) THEN BEGIN TPersonaggio (target).XP := TPersonaggio (target).maxXP; TPersonaggio (target).maxXP := 0; { Trova il livello di esperienza cui era giunto } tgtLevel := TPersonaggio (target).livello; WHILE TPersonaggio(target).baseHP[tgtLevel+1] > 0 DO BEGIN tgtLevel := succ (tgtLevel); target.maxHP := target.maxHP + TPersonaggio(target).baseHP[tgtLevel+1] + HPBonus (TPersonaggio(target).costituzione, TPersonaggio(target).classe); END; TPersonaggio (target).livello := tgtLevel; { Aggiorna XPÉ } TPersonaggio(target).XPtoNextLevel := FindNextXP (TPersonaggio(target).classe, tgtLevel); TPersonaggio(target).XPForThisLevel := FindNextXP (TPersonaggio(target).classe, tgtLevel-1); AddToTranscript (unNome, ktIsRestored, '', 0); CharacterHasChanged (TPersonaggio (target)); solved := TRUE END; 8: { resurrect } IF target.status[IsPC] AND target.status[IsDead] THEN BEGIN solved := TRUE; { Constitution survival check } IF Dado (1, 100) > Progressione (18 - TPersonaggio(target).costituzione) THEN BEGIN { Did survive } KillAllSpells (target); { You never know } FOR statusLoop := rs15 TO IsIll DO target.status[statusLoop] := FALSE; target.status[IsPC] := TRUE; target.isPoisoned := 0; target.HP := 1; AddToTranscript (unNome, ktIsResurrected, '', 0); SetPort (mainWindow); TMInvalRect (groupRect); END ELSE BEGIN KillAllSpells (target); { You never know } AddToTranscript (unNome, ktWontResurrect, '', 0); TPersonaggio(target).intelligenza := 0; TPersonaggio(target).saggezza := 0; TPersonaggio(target).costituzione := 0; TPersonaggio(target).carisma := 0; TPersonaggio(target).forza := 0; TPersonaggio(target).destrezza := 0 END { ELSE - wont't survive resurrection } END; { resurrection } 9: { Dispel magic } BEGIN KillAllSpells (target); solved := TRUE END; 10: { fly } BEGIN CASE attackingSpell.reserved OF 2{ do fly }: IF NOT target.status[IsFlying] THEN BEGIN target.status[IsFlying] := TRUE; AddToTranscript (unNome, ktFlies, '', 0); END; 1{ ground }: IF target.status[IsFlying] THEN BEGIN AddToTranscript (unNome, ktIsGrounded, '', 0); target.status[IsFlying] := FALSE; END; END; { case } solved := FALSE END; 11: { know alignment } BEGIN ParamText (unNome, Concat (': ', OutputAlignment (target.allineamento)), '', ''); i := AlertLord (rSpellAlert, 1, okButton); solved := TRUE; END; 12: { Faerie fire } BEGIN { Cambia la sua AC } IF attackingSpell.reserved <> 0 THEN target.AC := target.AC + attackingSpell.reserved ELSE target.AC := succ(target.AC); solved := FALSE END; 13: { Simulacrum } IF target.status[IsPC] AND (numPC <= kMaxCharInUI) THEN BEGIN solved := FALSE; Mondo[numPC] := TPersonaggio(target.Clone); { Fa in modo che l'incantesimo venga collegato al clone, e non all'originale } attackAdditionalInfo.target := Mondo[numPC]; numPC := numPC + 1; TMInvalRect (groupRect); END; 14: BEGIN { See beyond } ShowAllPlace; solved := TRUE END; 15: BEGIN { Teleport } solved := TRUE; CASE attackingSpell.reserved OF 0: DoLoadPlace (-1, '5'); 1: BEGIN locationStackPointer := 0; groupX := 0; DoLoadPlace (1000, '5'); { Start new scenario } END; OTHERWISE DoLoadPlace (attackingSpell.reserved, '8') END END; 16: BEGIN { Remove curse } solved := TRUE; IF target.status[IsPC] THEN FOR loop := Testa TO Sacco6 DO IF TPersonaggio(target).equipaggiamento[loop].data[0] { cursed } THEN ItemIsThrown (TPersonaggio(target).equipaggiamento[loop], TPersonaggio(target), TRUE, FALSE); END; 17: { Invisibility } IF attackingSpell.reserved = 0 THEN BEGIN { detect } solved := TRUE; cicloMostro := primoMostro; WHILE cicloMostro <> NIL DO BEGIN cicloMostro.status[IsInvisible] := FALSE; cicloMostro := cicloMostro.nextMonster END { while } END { detect } ELSE BEGIN { Become } solved := FALSE; target.status[IsInvisible] := TRUE; END; OTHERWISE BEGIN solved := TRUE; NewErrorAlert (kAlertStopAlert, errUnknownSpecialCode, 0) END; END; { case of special cases } { 2. incantesimi istantanei per il FightingSystem: codeSelector zero damage tipicamente maggiore di zero (pu˜ essere 0 per death magic?) durBase zero } OTHERWISE BEGIN solved := TRUE; IF (attackingSpell.duratBase <> 0) | (attackingSpell.codeSelector <> 0) THEN NewErrorAlert (kAlertStopAlert, errUnknownAttackForm, 0); END { otherwise } END; { case kind of spell } END; { with } CoreSpellSystem := solvedEND;{$S Magic}PROCEDURE SpellSystem2;{ Lo spell system in Dream 1.0 aveva al suo interno una serie di case.Sostanzialmente, ogni incantesimo controllava separatamente a che groundZeroandava applicato. In questa versione, SpellSystem2 si preoccupa di gestire groundZero, e chiamaSpellSystemCore garantendo un target individuale.SpellSystem, inoltre, restituiva un Boolean: nel casoFALSE era richiesto ulteriore elaborazione da parte del chiamante, chedoveva assegnare l'incantesimo al target.SpellSystem2 esegue personalmente l'assegnamento dell'incantesimo al target.SAREBBE BELLISSIMO INCORPORARE ANCHE IL CODICE DI INDIVIDUALATTACK}VAR all: EntityRef; unMostro: TMostro; gotOne, mustDisposeOriginal: Boolean; PROCEDURE DispatchSpellCode (useOriginal: Boolean; myTarget: TCreatura); VAR mustAttach: Boolean; BEGIN WITH attackAdditionalInfo DO BEGIN { Esegui l'incantesimo e scopri se va allegato al target } target := myTarget; mustAttach := NOT CoreSpellSystem; { Se va allegato, allegalo } IF mustAttach THEN { Qui bisogna lasciare target (campo di attackAdditionalInfo) e non myTarget (variabile locale) come parametro di GiveXxxxToCreature perchŽ alcuni incantesimi (per esempio Simulacrum) cambiano il target } IF useOriginal THEN BEGIN mustDisposeOriginal := FALSE; GiveSpellToCreature (attackingSpell, target) END ELSE GiveCloneToCreature (attackingSpell, target) END { with } END; BEGIN WITH attackAdditionalInfo DO BEGIN IF attackingSpell.specifiche[7] { kind of spell } THEN DoSoundAsync (sndClericSpell) ELSE DoSoundAsync (sndWizardSpell); mustDisposeOriginal := TRUE; CASE groundZero OF Individual, Caster: BEGIN IF (target <> NIL) AND target.status[IsPC] THEN { A volte target nil se l'inc. stato lanciato da trappola o oggetto } CharacterHasChanged (TPersonaggio (target)); DispatchSpellCode (TRUE, target) END; OneMonster: DispatchSpellCode (TRUE, target); AllGroup: BEGIN { Dai a tutti tranne che al caster una copia } FOR all := 0 TO numPC-1 DO IF StripAddress(target) = StripAddress(Mondo[all]) THEN BEGIN CharacterHasChanged (Mondo[all]); DispatchSpellCode (TRUE, Mondo[all]) END ELSE IF DistanzaInMosse (Mondo[all].whereAmI, origin) <= areaWidth THEN BEGIN CharacterHasChanged (Mondo[all]); DispatchSpellCode (FALSE, Mondo[all]); END END; { group } AllMonsters: BEGIN unMostro := primoMostro; gotOne:= FALSE; { Dai a tutti tranne che all'ultimo una copia } WHILE unMostro <> NIL DO BEGIN IF DistanzaInMosse (unMostro.whereAmI, origin) <= areaWidth THEN BEGIN DispatchSpellCode (NOT gotOne, unMostro); gotOne := TRUE END; unMostro := unMostro.nextMonster; END { while } END { AllMonsters } END; { case } IF mustDisposeOriginal THEN BEGIN attackingSpell.Free; attackingSpell := NIL END; END { with }END;(********************************************)(* *)(* Codice di bassissima manovalanza *)(* usato dal FightingSystem e dal codice *)(* decisionale successivo. *)(* *)(********************************************){$S LowLevel}FUNCTION Signum (x: Integer): Integer;BEGIN IF x < 0 THEN Signum := -1 ELSE IF x = 0 THEN Signum := 0 ELSE Signum := 1END;{$S LowLevel}FUNCTION CanMoveHere (coord: Point; mayFly: Boolean): Boolean;{ Chiamata dal mostro che si vuole muovere. Controlla che il luogo non sia occupatoda un altro mostro oppure impassable oppure out of bounds. }VAR result: Boolean; colleague: TMostro; foe: TPersonaggio;BEGIN { Non sarˆ per caso out of bounds? } IF NOT PtInrect (coord, arenaBounds) THEN BEGIN CanMoveHere := FALSE; Exit (CanMoveHere) END; { Posso muovere l“ se so volare, o se non c' ostacolo } result := (mayFly | NOT arena [coord.h, coord.v, 7]); { Se il risultato negativo, basta cos“. Ma se positivi debbo anche controllare che l“ non ci sia un altro mostro. } IF result THEN result := NOT PosToMonster (coord, colleague) & NOT PosToCharacter (coord, foe); CanMoveHere := resultEND;{$S LowLevel}FUNCTION CharIsHere (pos: point): BOOLEAN;{ A uso di HandleMovement }VAR charLoop: EntityRef;BEGIN charLoop := 0; WHILE (charLoop < numPC) & (longint(Mondo[charLoop].whereAmI) <> longint(pos)) DO charLoop := succ (charLoop); CharIsHere := charLoop < numPCEND;{$S LowLevel}FUNCTION Dir2Move (dirX, dirY: Integer): Char;{ Trasforma una direzione in una mossa }BEGIN IF dirY = -1 THEN BEGIN IF dirX = -1 THEN Dir2Move := '7' ELSE IF dirX = 0 THEN Dir2Move := '8' ELSE Dir2Move := '9' END { v < 0 } ELSE IF dirY = 0 THEN BEGIN IF dirX = -1 THEN Dir2Move := '4' ELSE IF dirX = 0 THEN Dir2Move := '5' ELSE Dir2Move := '6' END { v = 0 } ELSE BEGIN { v > 0 } IF dirX = -1 THEN Dir2Move := '1' ELSE IF dirX = 0 THEN Dir2Move := '2' ELSE Dir2Move := '3' END { v > 0 }END;{$S LowLevel}FUNCTION CanFleeHere (coord: Point; mayFly: Boolean): Boolean;{ Chiamata dal mostro che si vuole muovere. Controlla che il luogo dovevuole fuggire sia disponibile alla fuga }VAR result: Boolean; colleague: TMostro; foe: TPersonaggio;BEGIN { La soluzione preferibile uscire dall'arena } IF NOT PtInrect (coord, arenaBounds) THEN BEGIN CanFleeHere := TRUE; Exit (CanFleeHere) END; { Posso muovere l“ se so volare, o se non c' ostacolo } result := (mayFly | NOT arena [coord.h, coord.v, 7] & NOT PosToCharacter (coord, foe)); { Se il risultato negativo, basta cos“. Ma se positivo debbo anche controllare che l“ non ci sia un altro mostro. } IF result THEN result := NOT PosToMonster (coord, colleague); CanFleeHere := resultEND;{$S LowLevel}FUNCTION IsSoundEscapeMove (coord: Point): Boolean;{ Chiamata quando CanFreeHere ha restituito TRUE, esamina le vicinanze del postolibero. Se non ci sono pericoli, restituisce TRUE. Se c' un nemico vicinorestituisce FALSE }VAR loop: EntityRef; perilFound: Boolean; rectSurroundingEscapePos: Rect;BEGIN IF PtInrect (coord, arenaBounds) THEN IsSoundEscapeMove := TRUE ELSE BEGIN { Crea un rettangolo che comprenda le otto locazioni limitrofe } WITH rectSurroundingEscapePos DO BEGIN top := coord.v - 1; bottom := coord.v + 1; left := coord.h - 1; right := coord.h + 1 END; { Guarda se c' un personaggio dentro quel rettangolo } perilFound := FALSE; FOR loop := 0 TO numPC-1 DO IF PtInRect (Mondo[loop].whereAmI, rectSurroundingEscapePos) & { se vedo le creature invisibili me ne accorgo sempre) (SeesInvisible IN movingGuy.status | { Altrimenti me ne accorgo solo se sono visibili } NOT Mondo[loop].status[IsInvisible] THEN perilFound := TRUE; { Se non ne hai trovati dai il benestare } IsSoundEscapeMove := NOT perilFound END;END;{$S LowLevel}FUNCTION DistanzaDalBordo (fromWhere: point): Integer;VAR minDist, eval: Integer;BEGIN WITH fromWhere DO BEGIN { Pi vicino al bordo sinistro o superiore? } IF h < v THEN minDist := h ELSE minDist := v; { Il bordo destro pi vicino? } eval := arenaX + 1 - h; IF eval < minDist THEN minDist := eval; { Il bordo inferiore pi vicino? } eval := arenaY + 1 - v; IF eval < minDist THEN minDist := eval; END; DistanzaDalBordo := minDistEND;{$S LowLevel}FUNCTION FindAlignedFoe (targetAlign: TAllineamento): Integer;{ Cerca tra i PC un personaggio di questo allineamento. Se ce n' almeno uno,restituisce lo EntityRef del primo trovato. Altrimenti restituisce -1 }VAR found: Boolean; loop: Integer;BEGIN { Questo codice non prende in considerazione l'invisibilitˆ. L'idea sarebbe che se un mostro riesce in qualche modo a percepire l'allineamento, allora l'invisibilitˆ gli fa un baffo } loop := -1; REPEAT loop := succ (loop); found := (Mondo[loop].allineamento = targetAlign) UNTIL found | (loop = numPC-1); IF found THEN FindAlignedFoe := loop ELSE FindAlignedFoe := -1END;{$S LowLevel}FUNCTION FindClassedFoe (targetClass: TClasse): Integer;{ Cerca tra i PC un personaggio di questa classe. Se ce n' almeno uno,restituisce lo EntityRef del primo trovato. Altrimenti restituisce -1 }VAR found: Boolean; loop: Integer;BEGIN loop := -1; REPEAT loop := succ (loop); found := (Mondo[loop].classe = targetClass) AND { lo riconosco se non invisibile } (NOT Mondo[loop].status[IsInvisible] OR { o anche se invisibile ma lo vedo } movingGuy.status[SeesInvisible]) UNTIL found | (loop = numPC-1); IF found THEN FindClassedFoe := loop ELSE FindClassedFoe := kNoPCSelectedEND;{$S LowLevel}FUNCTION FindNearestColleague (myPos: Point; VAR distance: Integer): TMostro;{ Trova il pi vicino tra tutti gli amici (mostri) }VAR loopMostro, vicinMostro: TMostro; unaDist: Integer;BEGIN vicinMostro := primoMostro; { se sono solo... Bug fix 2.1 } loopMostro := primoMostro; distance := maxint; REPEAT IF Longint (myPos) <> Longint (loopMostro.whereAmI) THEN BEGIN unaDist := DistanzaInMosse (loopMostro.whereAmI, myPos); IF unaDist < distance THEN BEGIN distance := unaDist; vicinMostro := loopMostro END END; loopMostro := loopMostro.nextMonster UNTIL loopMostro = NIL; FindNearestColleague := vicinMostroEND; {$S LowLevel}FUNCTION FindNearestEnemy (myPos: Point; VAR hisIndex: EntityRef): TPersonaggio;{ Trova il pi vicino tra tutti i nemici (PC).New for v2.1: pu˜ restituire NIl se non trova alcuno (p. es. tutti invisibili) }VAR i: EntityRef; aChar: TPersonaggio; thisDist, minimumDist: Integer;BEGIN minimumDist := maxint; hisIndex := -1; { Twist: questo apparentemente non serve, perche' dovremmo trovare sempre un personaggio da attaccare nell IF thisDist < minimumDist che segue. In realta' puo' accadere che tutti i personaggi sono morti. } FOR i := numPC-1 DOWNTO 0 DO BEGIN aChar := Mondo[i]; IF NOT aChar.status[IsDead] & (aChar.whereAmI.h > 0) & { lo riconosco se non invisibile } (NOT aChar.status[IsInvisible] | { o anche se invisibile ma lo vedo } movingGuy.status[SeesInvisible]) THEN BEGIN thisDist := DistanzaInMosse (aChar.whereAmI, myPos); { Se la cosa deterministica - cio se l'IF seguente viene tolto - i mostri tendono ad attaccare in massa lo stesso personaggio. Questo if serve a fare s“ che, quando pi personaggi sono alla stessa distanza, i mostri si dividano casualmente tra tutti i bersagli possibili } IF (thisDist = minimumDist) & (Dado (1, 2) = 2) THEN BEGIN minimumDist := thisDist; hisIndex := i; END; { if is near } IF thisDist < minimumDist THEN BEGIN minimumDist := thisDist; hisIndex := i; END; { if is near } END { if is not dead } END; { for } IF hisIndex = -1 THEN FindNearestEnemy := NIL ELSE FindNearestEnemy := Mondo[hisIndex];END;{$S LowLevel}FUNCTION FindWeakestEnemy: EntityRef;{ Trova il pi debole tra tutti i nemici (PC), e restituisce il EntityRef }VAR i, chosen: EntityRef; thisHP, minimumHP: Integer;BEGIN chosen := kNoPCSelected; { New for v2.1 } minimumHP := maxint; FOR i := 0 TO numPC-1 DO BEGIN thisHP := Mondo[i].HP; IF (thisHP < minimumHP) & NOT Mondo[i].status[isDead] & (Mondo[i].whereAmI.h > 0) & { lo riconosco se non invisibile } (NOT Mondo[i].status[IsInvisible] | { o anche se invisibile ma lo vedo } movingGuy.status[SeesInvisible]) THEN BEGIN minimumHP := thisHP; chosen := i; END; END; FindWeakestEnemy := chosenEND;(********************************)(* *)(* ¥¥¥¥ HereToThere *)(* *)(* Codice che decide quale *)(* movimento effettuare per *)(* dirigersi verso un *)(* obiettivo *)(* *)(* ¥¥¥¥ OKPanic *)(* *)(* Codice che decide quale *)(* movimento effettuare per *)(* fuggire da un avversario *)(* limitrofo *)(* *)(* ¥¥¥¥ GoneWithTheWind *)(* *)(* Codice che decide quale *)(* movimento effettuare quando *)(* il mostro in panico ma *)(* non c' pericolo immediato *)(* *)(********************************){$S LowLevel}FUNCTION HereToThere (base, dest: Point; intelligent, canFly: Boolean): Char;{ Dato un punto di partenza e un punto di arrivo, trova una mossa peravvicinarsi. Tiene conto delle asperitˆ del terreno. Se intelligent true, gira anche intorno agli ostacoli. }VAR delta: Point; { Distanza da valicare per raggiungere l'obiettivo } dirX, dirY: Integer; scambiato: Boolean; { Ho scambiato le X con le Y ? } swap: Integer; { Usato per lo scambio } PROCEDURE TryThis (dirX, dirY: INTEGER); { Controlla la fattiblitˆ di una mossa; se la trovi attuabile esci da HereToThere restituendola } VAR dest: point; BEGIN if scambiato THEN BEGIN swap := dirX; dirX := dirY; dirY := swap END; WITH dest DO BEGIN v := base.v + dirY; h := base.h + dirX; { Posso andarci se sono giˆ l“ o se una mossa valida } IF EqualPt (base, dest) | CanMoveHere (dest, canFly) THEN BEGIN { OK! } HereToThere := Dir2Move (dirX, dirY); Exit (HereToThere); END; END { with } END; { TryThis } BEGIN { HereToThere } WITH delta DO BEGIN { Poni in delta la distanza da coprire } v := dest.v - base.v; h := dest.h - base.h; { Trova l'accoppiata di direzioni che mi porta l“ per via pi diretta } dirX := Signum (h); dirY := Signum (v); { If we are close to, it's done. Otherwise we have work to do. This IF makes the code quicker, and guarantees that it works even if the destination is outside the arena.Ê} IF canFly | ((abs (v) < 2) & (abs (h) < 2)) THEN BEGIN HereToThere := Dir2Move (dirX, dirY); Exit (HereToThere) END; { OK, non cos“ facile. Innanzitutto semplifica, in modo che la distanza da percorrere sull'asse Y sembri la maggiore } IF abs(h) > abs(v) THEN BEGIN swap := v; v := h; h := swap; swap := dirX; dirX := dirY; dirY := swap; scambiato := TRUE END ELSE scambiato := FALSE; { Ora posso supporre che deltaV sia maggiore di deltaH } { La mia mossa preferita fa s“ che la distanza in entrambe le direzioni diminuisca. } TryThis (dirX, dirY); IF abs(h) > 0 THEN BEGIN { La mia seconda mossa preferita diminuisce la distanza nella sola direzione maggiore } { La distanza sull'asse x maggiore di zero e resta invariata } TryThis (0, dirY); { Terza mossa preferita. Questa la situazione (* sono io, $ l'obiettivo, W sono ostacoli: . . . $ . . . . . . . . . W W . . * . . Vado in alto e a sinistra. } TryThis (-dirX, dirY); END ELSE BEGIN { La distanza sull'asse x zero, cio ho l'avversario davanti a me e un ostacolo tra noi. Prova a scartarlo a dx o sx } TryThis (1, dirY); TryThis (-1, dirY) END; { Quarta mossa preferita (solo se intelligenza almeno 1) } IF intelligent THEN BEGIN { Questa la situazione (* sono io, $ l'obiettivo, W sono ostacoli: . . . $ . . . . . . . . W W W . . * . . Provo ad evitare gli ostacoli muovendomi a destra. } TryThis (dirX, 0); { Quinta mossa preferita. Questa la situazione (* sono io, $ l'obiettivo, W sono ostacoli: . . . $ . . . . . . . . W W W . . * W . Mi muovo a sinistra } TryThis (-dirX, 0); END; { If intelligent } { Se nessuna delle precedenti va bene } TryThis (0, 0); { Questa va bene di sicuro, perchŽ resto fermo. } END { with }END; { HereToThere }{$S LowLevel}Function OKPanic (panicSource: TCreatura; myPos: point; myIntelligence: Integer; canFly: Boolean): Char;{ Sono nel panico, perchŽ ho in panicSource un avversario da cui vogliofuggire. Scelgo una mossa.SUPPONE CHE panicSource mi sia limitrofo. }VAR directionOfPerilX, directionOfPerilY: Integer; trePossibiliFughe: ARRAY [1..3] OF Point; { in v e h la direzione in cui voglio muovermi, cio -1, 0 oppure 1 } { Algoritmo. Se sono non-intelligente o intelligenza animale, mi allontano dall'avversario pi direttamente possibile, a costo di finire vicino a un altro avversario. Se sono un po' intelligente, mi allontano senza finire nelle grinfie di un altro. Se sono molto intelligente, cerco anche di avvicinarmi al bordo dell'arena (*** TBD ***) In ogni caso, se non posso fuggire torno ad attaccare per un round. } PROCEDURE TryIt (whichOfTheThree: Integer); VAR endPoint: point; BEGIN WITH trePossibiliFughe[whichOfTheThree] DO BEGIN endPoint.v := myPos.v + v; endPoint.h := myPos.h + h; IF CanFleeHere (endPoint, canFly) & ((myIntelligence < 2) | (IsSoundEscapeMove (endPoint))) THEN BEGIN OKPanic := Dir2Move (h, v); Exit (OKPanic) END { If can flee } END { with } END; { TryIt }BEGIN WITH myPos DO BEGIN { Allora, da dove viene il pericolo? } directionOfPerilX := Signum (panicSource.whereAmI.h - h); directionOfPerilY := Signum (panicSource.whereAmI.v - v); { Ci sono tre possibili vie di fuga che mi distanziano da luiÉ La prima quella direttamente opposta } trePossibiliFughe[1].v := - directionOfPerilY; trePossibiliFughe[1].h := - directionOfPerilX; { Se direttamente su un lato, le altre due direzioni sono le mosse diagonali } IF directionOfPerilX = 0 THEN BEGIN trePossibiliFughe[2].v := - directionOfPerilY; trePossibiliFughe[2].h := 1; trePossibiliFughe[3].v := - directionOfPerilY; trePossibiliFughe[3].h := - 1 END ELSE IF directionOfPerilY = 0 THEN BEGIN trePossibiliFughe[2].v := 1; trePossibiliFughe[2].h := - directionOfPerilX; trePossibiliFughe[3].v := - 1; trePossibiliFughe[3].h := - directionOfPerilX END { Se adiacente in uno spigolo, le altre due mosse sono parallele agli assi } ELSE BEGIN trePossibiliFughe[2].v := 0; trePossibiliFughe[2].h := - directionOfPerilX; trePossibiliFughe[3].v := - directionOfPerilY; trePossibiliFughe[3].h := 0 END; { adiacente a uno spigolo } END; { with } { Va bene, adesso provaci } TryIt (1); TryIt (2); TryIt (3); { Spalle al muro. Allora torna ad attaccare } OKPanic := 'A';END;FUNCTION GoneWithTheWind (myPos: Point; mayFly: Boolean): Char;{ Sono in panico, ma il mio persecutore non vicino, nŽ altro avversariomi sta vicino }VAR mossePossibili: ARRAY [-1..1, -1..1] OF Integer; x, y, bestScoreYet, dalBordo, dallAvversario: Integer; posEvaluated, bestMoveYet: Point; closestFoe: TPersonaggio; hisIndex: EntityRef;BEGIN{ Algoritmo: Valuto ciascuna delle otto possibili caselle secondo un sistema di pesi:5 punti per ogni casella di distanza dal nemico pi vicino,-3 punti per ogni casella di distanza dal bordo,} { Casi migliori } IF (myPos.h = 1) THEN BEGIN GoneWithTheWind := '4'; Exit (GoneWithTheWind) END; IF (myPos.h = arenaX) THEN BEGIN GoneWithTheWind := '6'; Exit (GoneWithTheWind) END; IF (myPos.v = 1) THEN BEGIN GoneWithTheWind := '8'; Exit (GoneWithTheWind) END; IF (myPos.v = arenaY) THEN BEGIN GoneWithTheWind := '2'; Exit (GoneWithTheWind) END; { Va bene, allora pensiamoci } bestScoreYet := - maxint; FOR x := -1 TO 1 DO FOR y := -1 TO 1 DO BEGIN WITH posEvaluated DO BEGIN v := myPos.v + y; h := myPos.h + x END; IF NOT CanFleeHere (posEvaluated, mayFly) THEN Leave; { Jump to next iteration } closestFoe := FindNearestEnemy (posEvaluated, hisIndex); dalBordo := DistanzaDalBordo (posEvaluated); IF hisIndex = -1 THEN dallAvversario := MAXINT ELSE dallAvversario := DistanzaInMosse (posEvaluated, closestFoe.whereAmI); { Se una mossa ideale - nessun avversario vicino, a ridosso del bordo - sceglila senza por tempo in mezzo } IF (dalBordo = 1) & (dallAvversario > 1) THEN BEGIN GoneWithTheWind := Dir2Move (x, y); Exit (GoneWithTheWind) END; { Altrimenti calcola il valore della posizione } mossePossibili[x,y] := dallAvversario * 5 - dalBordo * 3 - ord (arena [posEvaluated.h, posEvaluated.v, 7]) * 100; IF mossePossibili[x,y] > bestScoreYet THEN BEGIN bestScoreYet := mossePossibili[x,y]; bestMoveYet.v := y; bestMoveYet.h := x END; { Trovata mossa migliore } END; { ciclo for } GoneWithTheWind := Dir2Move (bestMoveYet.h, bestMoveYet.v)END;(************** Fine del codice decisionale **********************){$S Characters}PROCEDURE SeppellisciMostro (m: TMostro);VAR scan: TMostro;BEGIN { Morale } mmm := mmm - 10; numMonsters := pred (numMonsters); IF StripAddress (m) = StripAddress (primoMostro) THEN primoMostro := primoMostro.nextMonster ELSE BEGIN { Trova l'elemento precedente nella lista } scan := primoMostro; WHILE StripAddress (m) <> StripAddress (scan.nextMonster) DO scan := scan.nextMonster; scan.nextMonster := m.nextMonster END; KillAllSpells (m); m.Free;END;{$S LowLevel}PROCEDURE DoWanderingMonsters (refID: INTEGER);VAR numAlternative, i, chosenMonsterID: Integer; wanderingHandle: Handle; scanner: Ptr;BEGIN { Sanity check } IF numPC = 0 THEN Exit (DoWanderingMonsters); wanderingHandle := MyGetResource (resWanderingMonsters, refID, TRUE, TRUE); scanner := StripAddress(wanderingHandle^); numAlternative := GetIntegerFromRes (scanner); { Se ci sono quattro mostri possibili, numAlternative vale 3 } { Trova un gruppo di mostri a caso } FOR i := 1 TO Dado (1, numAlternative+1) DO chosenMonsterID := GetIntegerFromRes (scanner); { Liberati della risorsa } HUnlock (wanderingHandle); { Invoca il combattimento } FightingSystem (chosenMonsterID, FALSE, 0);END;(****************************************************************************)(* *)(* T H E *)(* D R E A M *)(* F I G H T I N G *)(* S Y S T E M *)(* *)(****************************************************************************){$S DefProcs}PROCEDURE PlotCreatura (it: TCreatura; come:INTEGER);{ Chiamata sia da DrawArena sia da WakeUpLittleSuzie per disegnare le creature sulcampo di combattimento. Se come vale ttNone disegna solo l'icona. Se valettSelected le disegna selezionate E con un rettangolo marcato d'intorno, di modoche si veda anche se non si usano solo i colori standard. } VAR r: Rect; BEGIN IF NOT PtInRect (it.whereAmI, arenaBounds) THEN Exit (PlotCreatura); { é fuggito, non disegnarlo } FightCoord2ArenaRect (it.whereAmI.h, it.whereAmI.v, r); IF it.status[IsBig] THEN { new for v2 } InsetRect (r, -16, -16); IF come = ttInvisible THEN { Just make it disappear } TMInvalRect (r) ELSE BEGIN it.Draw (r, come+$8000); { $8000 provoca l'uso delle icone a figura intera } IF it.status[IsDead] THEN exit (PlotCreatura); IF come = ttSelected THEN BEGIN PenSize(2, 2); FrameRect (r); PenSize (1, 1); END { if selected } END { not invisible } END;{$S DefProcs}PROCEDURE DrawArena (myWin: WindowPtr);VAR everybody: EntityRef; everyMonster: TMostro; r1, r2: Rect;BEGIN { Lo sfondo } SetRect(r1,0,0,BSL(arenaX, 5),BSL(arenaY, 5)); r2 := r1; OffsetRect(r2,32,32); IF LockPixels (arenaWorld^.portPixMap) THEN BEGIN CopyBits(bitmapptr(arenaWorld^.portPixMap^)^, myWin^.portBits, r1, r2, srcCopy, nil); UnlockPixels (arenaWorld^.portPixMap); END; { Personaggi. é un algoritmo dello stracavolo } FOR everybody := 0 to numPC-1 DO IF StripAddress(movingGuy) = StripAddress(Mondo[everybody]) THEN PlotCreatura (Mondo[everybody], ttSelected) ELSE PlotCreatura (Mondo[everybody], ttNone); { Mostri } everyMonster := primoMostro; WHILE everyMonster <> NIL DO BEGIN IF StripAddress(movingGuy) = StripAddress(everyMonster) THEN PlotCreatura (everyMonster, ttSelected) ELSE PlotCreatura (everyMonster, ttNone); everyMonster := everyMonster.nextMonster END;END;{$S LowLevel}PROCEDURE TMostro.Draw (where: rect; how: INTEGER);BEGIN IF NOT status[IsInvisible] THEN INHERITED Draw (where, how)END;{$S LowLevel}Procedure TMostro.Init (ref: integer);{ Creates one new instance of a monster }CONST kDiceForMonsters = 8;VAR myMonsterResource, mySound: Handle; scanner: Ptr; FUNCTION LongintToPowerSet (l: Longint): PowerSet; { rewritten for v2.1 } VAR result: PowerSet; BEGIN Blockmove (@l, @result, SizeOf (Longint)); LongintToPowerSet := result END;begin { Let the low level code to do its own init } INHERITED Init (ref); { Read from scenario the monster descriptor data if necessary } myMonsterResource := MyGetResource (resMonster, ref, TRUE, TRUE); scanner := Ptr (ORD4(StripAddress(myMonsterResource^))+1); { Discard first byte [=num appearing] } { Start reading. } allineamento := TAllineamento (GetByteFromRes (scanner)); HP := Dado (GetByteFromRes(scanner), kDiceForMonsters); maxHp := HP; THACO := GetByteFromRes (scanner); attacchiPerDueRound := GetByteFromRes (scanner); dannoInDadi := GetByteFromRes (scanner); dimDadi := GetByteFromRes (scanner); baseDamage := 0; { Non previsto un base damage per mostri } AC := GetByteFromRes (scanner); IF AC > 127 THEN AC := AC - 256; { Complemento a due, bug fix 2.0 } XP := GetIntegerFromRes (scanner); IF XP < 0 THEN XP := 65536 + XP; { Bug fix 2.1 } specialAbilities := GetWBFromRes (scanner); { Set status } status[IsFlying] := specialAbilities[5]; status[SeesInvisible] := specialAbilities[7] OR (intelligenza > 5); { I mostri molto intelligenti intuiscono dove stiano gli avversari invisibili } status[IsInvisible] := specialAbilities[6]; status[IsBig] := specialAbilities[2]; { New for v2 } status[IsStoned] := FALSE; status[IsIll] := FALSE; status[IsDead] := FALSE; intelligenza := GetByteFromRes (scanner); { Mi arriva come signed byte, non unsigned } morale := BAnd ($00FF, GetByteFromRes (scanner)); IF (morale > 254) THEN morale := 20000; { Cos“ sono sicuro che non scappa senza fare check } nome := GetStringFromRes (scanner); icon := GetIntegerFromRes (scanner); specialAttacks := LongintToPowerSet(GetLongintFromRes (scanner)); attackSelector := GetIntegerFromRes (scanner); specialDefenses := LongintToPowerSet(GetLongintFromRes (scanner)); defenseSelector := GetIntegerFromRes (scanner); { See if I have a related sound } mySound := MyGetResource ('snd ', ref, FALSE, TRUE); IF mySound = NIL THEN mySoundID := 0 ELSE mySoundID := ref; { Put myself in the list } nextMonster := primoMostro; primoMostro := self; numMonsters := succ (numMonsters); { Clean up } mdel := NIL; mdelCounter := 0; HUnLock (myMonsterResource);end;{$S LowLevel}FUNCTION TMostro.Kill: boolean;{ Caller has to rimove me from the list }begin IF mdel <> NIL THEN ReleaseResource (Handle (mdel)); DoSoundAsync (sndMonsterDead); Kill := INHERITED Killend;{$S LowLevel}Function TMostro.Move: char;(*** New for v1.1: supporta la MDeL. ***){ Risultati possibili: i soliti (1..9) pi: A: voglio attaccare. Dichiaro di essere in grado di farlo.}VAR solved, decided: Boolean; nearestFoe: TPersonaggio; nearestFriend: TMostro; hisIndex: EntityRef; hisDistance: Integer; myName: String; PROCEDURE IndividualCastThis (spellID: Integer); { Ho deciso di lanciare un incantesimo sulla creatura i cui dati sono in nearestFoe - hisIndex (altro mostro o personaggio che sia) } VAR spell: TIncantesimo; BEGIN decided := TRUE; spell := DoCastSpell (spellID, kCasterLevelForItems, myName, whereAmI); IF spell = NIL THEN Move := '*' { Bug fix 1.6 - spesso a questo punto il target giˆ solved } ELSE WITH attackAdditionalInfo DO IF NOT targetSolved THEN BEGIN Move := 'K'; target := nearestFoe; targetRef := hisIndex; { Centra gli incantesimi d'attacco sul bersaglio designato, gli altri su me stesso } CASE groundZero OF Caster: BEGIN pivot := whereAmI; groundZero := Individual { Colpisci me } END; AllGroup: BEGIN pivot := whereAmI; groundZero := AllMonsters { Colpisci i miei colleghi mostri! (Invisibility, cureÉ) } END; AllMonsters: BEGIN pivot := target.whereAmI; groundZero := AllGroup; { Colpisci i miei nemici, i PC! } END; OneMonster: BEGIN pivot := target.whereAmI; groundZero := Individual; { Colpisci il PC specificato! } END; OTHERWISE pivot := target.whereAmI END; { case } targetSolved := TRUE END; { with } END; { cast } PROCEDURE IndividualAttackThis (how: KindOfAttack); { Ho deciso di attaccare la creatura i cui dati sono in nearestFoe-hisIndex. Compila il record con i suoi dati. Se how Weapon, questa procedura trova il metodo di attacco pi micidiale tra quelli posseduti dal mostro } BEGIN decided := TRUE; Move := 'A'; WITH attackAdditionalInfo DO BEGIN damage := baseDamage + Dado (dannoInDadi, dimDadi); attackingSpell := NIL; attackerTHACO := THACO; STforNone := TRUE; IF how = Weapon THEN BEGIN IF specialAttacks[Curse] & NOT nearestFoe.specialdefenses[Curse] THEN kind := Curse ELSE IF specialAttacks[DeathMagic] THEN kind := DeathMagic ELSE IF specialAttacks[TurnToStone] THEN BEGIN kind := TurnToStone; STForNone := FALSE; END ELSE IF specialAttacks[LevelDrain] THEN BEGIN STForNone := TRUE; kind := LevelDrain END ELSE IF specialAttacks[Poison] THEN kind := Poison ELSE IF specialAttacks[Illness] THEN kind := Illness ELSE BEGIN STForNone := FALSE; { No ST from physical attack } IF specialAttacks[WeaponPlusFive] THEN kind := WeaponPlusFive ELSE IF specialAttacks[WeaponPlusFour] THEN kind := WeaponPlusFour ELSE IF specialAttacks[WeaponPlusThree] THEN kind := WeaponPlusThree ELSE IF specialAttacks[WeaponPlusTwo] THEN kind := WeaponPlusTwo ELSE IF specialAttacks[WeaponPlusOne] THEN kind := WeaponPlusOne ELSE kind := Weapon END END ELSE kind := how; targetRef := hisIndex; target := nearestFoe; pivot := nearestFoe.whereAmI; groundZero := Individual; END; { Se ho un ruggito, ruggisco } IF mySoundID > 0 THEN DoSoundAsync (mySoundID); END; PROCEDURE FleeHim; { Ho deciso di fuggire dalla creatura indicata in nearestFoe, che mi sta vicina } VAR mossa: Char; BEGIN WITH memory DO BEGIN whom := hisIndex; action := Panic END; decided := TRUE; { Trova la direzione nella quale lui mi attacca, e fuggi dalla direzione opposta } mossa := OKPanic (nearestFoe, whereAmI, intelligenza, status[IsFlying]); IF mossa = 'A' THEN individualAttackThis (Weapon) ELSE Move := mossa END; PROCEDURE JustGoAway; BEGIN decided := TRUE; move := GoneWithTheWind (whereAmI, status[IsFlying]) END; PROCEDURE FleeFrom (posToFleeFrom: Point); VAR fakeDest: point; BEGIN decided := TRUE; { Usiamo HereToThere costruendo un punto opposto a quello da cui fuggire } WITH fakeDest DO BEGIN v := posToFleefrom.v - whereAmI.v; h := posToFleefrom.h - whereAmI.h END; Move := HereToThere (whereAmI, fakeDest, TRUE, status[IsFlying]) END; PROCEDURE SolveMonsterTarget (code: Integer); { Risolve i codici "monster target" definiti per la mdel, ponendo il risultato in nearestFoe e hisDistance. alignment of chosen foe, or 10+class of chosen foe, or 20+serial id of foe (zero-based), or 30: weakest foe 31: nearest foe 50+serial id of companion (zero-based), or 100: myself} LABEL 999; VAR foeID: EntityRef; i: Integer; foe: TCreatura; otherMonster: TMostro; PROCEDURE InternalFillInAttackForTargetMonster; BEGIN WITH attackAdditionalInfo DO BEGIN targetSolved := TRUE; damage := 0; attackerTHACO := THACO; target := otherMonster; targetRef := kNoPCSelected; origin := whereAmI; pivot := otherMonster.whereAmI; attackingSpell := NIL; END; { nearestFoe := otherMonster; } hisDistance := distanzaInMosse (whereAmI, otherMonster.whereAmI); END; BEGIN solved := FALSE; attackAdditionalInfo.targetSolved := FALSE; REPEAT CASE code OF 0..8: BEGIN foeID := FindAlignedFoe (TAllineamento(code)); solved := foeID <> kNoPCSelected; END; 10..15: BEGIN foeID := FindClassedFoe (TClasse (code-10)); solved := foeID <> kNoPCSelected; END; 20..28: BEGIN foeID := code-20; solved := (Mondo[foeID] <> NIL) END; 50..99: BEGIN { Find the target } otherMonster := primoMostro; i := code-50; WHILE (otherMonster.nextMonster <> NIL) AND (i>0) DO BEGIN i := pred (i); otherMonster := otherMonster.nextMonster END; { while } { Prepare target data } InternalFillInAttackForTargetMonster; { remember that we already solved everything } solved := TRUE; foeID := kNoPCSelected; code := 100 END; 30: BEGIN foeID := FindWeakestEnemy; solved := foeID <> kNoPCSelected END; 31: BEGIN foe := FindNearestEnemy (whereAmI, foeID); solved := foeID <> kNoPCSelected; END; 100: BEGIN otherMonster := self; foeID := kNoPCSelected; InternalFillInAttackForTargetMonster; solved := TRUE END; { case 100 } OTHERWISE DeathAlert (errUnknownMdelTarget, code) END; { case } IF NOT solved THEN BEGIN IF code <> 31 THEN code := 31 { Next round, Go for anyone } ELSE Goto 999; { Panic! No target available } END UNTIL solved;999: IF NOT solved THEN BEGIN hisIndex := kNoPCSelected; nearestFoe := NIL; hisDistance := MAXINT; WITH attackAdditionalInfo DO BEGIN targetSolved := TRUE; STforNone := FALSE; STforHalf := FALSE; damage := 0; attackerTHACO := THACO; target := NIL; targetRef := kNoPCSelected; origin := whereAmI; pivot := nearestFoe.whereAmI; attackingSpell := NIL; END; Exit (SolveMonsterTarget); END; { Now fill in answer record } IF code < 50 THEN BEGIN nearestFoe := Mondo[foeID]; hisDistance := DistanzaInMosse (whereAmI, nearestFoe.whereAmI); WITH attackAdditionalInfo DO BEGIN targetSolved := TRUE; STforNone := FALSE; STforHalf := FALSE; damage := 0; attackerTHACO := THACO; target := nearestFoe; targetRef := foeID; origin := whereAmI; pivot := nearestFoe.whereAmI; attackingSpell := NIL; END; END; hisIndex := foeID { Serve a IndividualCastThis - bug fix 1.5.1 } END; { SolveMonsterTarget }LABEL 1;begin myName := nome; { OK. So what do I do now? } decided := FALSE; { Do I have a Mdel attached? } IF mdel <> NIL THEN BEGIN { Yes. Is there a move for me in there? }1: mdelCounter := Succ (mdelCounter); decided := GetHandleSize(Handle(mdel))>= mdelCounter * SizeOf (MonsterMove); IF decided THEN BEGIN HLock (Handle (mdel)); WITH mdel^^[mdelCounter] DO BEGIN action := chr(BAnd(ord(action), $007F)); CASE action OF '0'..'9': Move := Action; { This one was easy } 'M': BEGIN { Localizza la mia destinazione } SolveMonsterTarget (direct); decided := FALSE; { Ok, un attacco? } IF (direct < 50) THEN IF (hisDistance = 1) AND specialAttacks[weapon] THEN IndividualAttackThis (Weapon) ELSE IF (hisDistance <= attackSelector) AND specialAttacks[missile] THEN IndividualAttackThis (Missile); IF nearestFoe = NIL THEN BEGIN { New for v2.1 } decided := TRUE; Move := '5'; END; IF NOT decided THEN BEGIN Move := HereToThere (whereAmI, nearestFoe.whereAmI, intelligenza > 0, status[IsFlying]); decided := TRUE END END; 'K': BEGIN SolveMonsterTarget (indirect); IndividualCastThis (direct) END; 'I': decided := FALSE; 'S': BEGIN KillText; TextOut (direct, FALSE); IF indirect <> 0 THEN DoSoundAsync (indirect); Goto 1; END; 'F': BEGIN SolveMonsterTarget (direct); FleeHim END; '?': BEGIN { Goto } { New for v 1.3: Se direct vale 0, il confronto con MaxHP } IF direct = 0 THEN BEGIN IF hp < maxHP THEN mdelCounter := indirect END ELSE IF hp < direct THEN mdelCounter := indirect; Goto 1 END; OTHERWISE DeathAlert (errUnknownMdelAction, ord(action)) END; { case - with } END; { with } HUnlock (Handle (mdel)); END; { If I could decide what to do via the mdel, then I'm done } IF decided THEN Exit (Move); END; { Mdel code } { Do I have a target? } IF (memory.whom > kNoPCSelected) & (Mondo[memory.whom].status[IsDead] | (NOT PtInRect (Mondo[memory.whom].whereami, arenaBounds))) THEN { If dead or away, then choose another } memory.whom := kNoPCSelected; { Is somebody very, very close? } nearestFoe := FindNearestEnemy (whereAmI, hisIndex); { Attn: hisIndex viene usato nel case molto pi sotto } hisDistance := distanzaInMosse (whereAmI, nearestFoe.whereAmI); IF hisDistance = 1 THEN BEGIN { Focus on this guy } WITH attackAdditionalInfo DO BEGIN target := nearestFoe; pivot := nearestFoe.whereAmI END; CASE memory.action OF Fight: IF specialAttacks[Weapon] THEN IndividualAttackThis (Weapon) ELSE { Sono un arcere, e un avversario mi ha raggiunto. Teliamo! } FleeHim; Flank, Group, Stand: IF specialAttacks[Weapon] THEN BEGIN WITH memory DO BEGIN whom := hisIndex; action := Fight END; IndividualAttackThis (Weapon) END ELSE FleeHim; Panic: FleeHim; Friend: IF (nearestFoe.allineamento = allineamento) THEN IF (nearestFoe.carisma > 14) THEN BEGIN AddToTranscript (myName, ktIsFriend, '', 0); WITH attackAdditionalInfo DO targetSolved := FALSE; IndividualCastThis (kSpelCureWound); decided := TRUE; memory.action := Panic END ELSE BEGIN decided := TRUE; AddToTranscript (myName, ktNodsAndGoes, '', 0); memory.action := Panic END ELSE BEGIN decided := TRUE; IF memory.whom > kNoPCSelected THEN Move := HereToThere (whereAmI, nearestFoe.whereAmI, intelligenza > 3, status[IsFlying]) ELSE BEGIN memory.whom := FindAlignedFoe (allineamento); AddToTranscript (myName, ktTremblesAndSniffs, '', 0); IF memory.whom = kNoPCSelected THEN memory.action := Panic END; END; END; { case action when foe is near } END { if foe is near } ELSE BEGIN { foe is not near } IF ((memory.action = Stand) OR (memory.action = Group)) AND specialAttacks[Missile] THEN BEGIN memory.action := Fight; memory.whom := hisIndex END; CASE memory.action OF Panic: BEGIN CASE intelligenza OF 0: BEGIN { Tipo scarafaggio: sto semplicemente fermo } decided := TRUE; Move := '5' END; 1: { Fuggo dalla fonte di panico e non mi importa di altri } IF memory.whom = kNoPCSelected THEN JustGoAway ELSE FleeFrom (Mondo[memory.whom].whereAmI); 2..7: { Cerco di uscire dall'arena } JustGoAway; OTHERWISE { Se qualcuno ha ucciso la persona di cui avevo paura, allora non ho pi paura. } IF memory.whom = kNoPCSelected THEN BEGIN memory.action := Fight; morale := morale + 15; { Per stavolta non muovo, mentre "ci penso" } decided := TRUE; Move := '5' END ELSE JustGoAway END; { case } END; Friend: BEGIN decided := TRUE; IF memory.whom > kNoPCSelected THEN Move := HereToThere (whereAmI, nearestFoe.whereAmI, intelligenza > 3, status[IsFlying]) ELSE BEGIN memory.whom := FindAlignedFoe (allineamento); AddToTranscript (myName, ktTremblesAndSniffs, '', 0); IF memory.whom = kNoPCSelected THEN memory.action := Panic END; END; Fight: BEGIN { So sparare? } IF specialAttacks[Missile] THEN BEGIN { Se si, ho giˆ un obiettivo? } IF memory.whom > kNoPCSelected THEN { Se si, entro il mio raggio di fuoco? } IF DistanzaInMosse (whereAmI, Mondo[memory.whom].whereAmI) <= attackSelector THEN BEGIN hisIndex := memory.whom; nearestFoe := Mondo[hisIndex]; { Si, tutto bene. } IndividualAttackThis (Missile) END; { Non ho obiettivo, oppure il vecchio obiettivo oltre il mio raggio d'azione. Ne trovo un altro. } IF hisDistance <= attackSelector THEN BEGIN { Ho un possibile avversario } memory.whom := hisIndex; IndividualAttackThis (Missile); END ELSE BEGIN { Avvicinati al bersaglio } decided := TRUE; IF memory.whom = kNoPCSelected THEN Move := HereToThere (whereAmI, nearestFoe.whereAmI, intelligenza > 0, status[IsFlying]) ELSE Move := HereToThere (whereAmI, Mondo[memory.whom].whereAmI, intelligenza > 0, status[IsFlying]) END; { avvicinati al bersaglio } END { Sono un arcere } ELSE BEGIN { Sono un combattente. Mi avvicino all'avversario } Move := HereToThere (whereAmI, nearestFoe.whereAmI, intelligenza > 0, status[IsFlying]); decided := TRUE END; END; { Fight } Group: BEGIN { La mia azione Group, non so che fare. } nearestFriend := FindNearestColleague (whereAmI, hisDistance); IF hisDistance > 1 THEN Move := HereToThere (whereAmI, nearestFriend.whereAmI, TRUE, status[IsFlying]) ELSE Move := '*'; { Mi sono giˆ riunito ai miei colleghi. Sto fermo } decided := TRUE; END; { Case group } Flank, Stand: BEGIN decided := TRUE; Move := '*' END; { Stand } END { case action of } END; { foe is not near } IF NOT decided THEN BEGIN Move := '*'; SysBeep (5) ENDend;{$S LowLevel}FUNCTION TMostro.Time (amountInMin: integer): boolean;BEGIN IF specialAbilities[4] { regenerates } & (HP < maxHP) THEN HP := succ (HP); Time := INHERITED Time (amountInMin)END;{$S Characters}Procedure FightingSystem (ref: INTEGER; encounter: BOOLEAN; NPCinvolved: Integer);LABEL 1, 998;VAR neededObjectPlace: Storage; { Where a group char has the needed item } unMostro: TMostro; { Per scandire il gruppo di mostri } encounterSpecifics: BitsInByte; { I flag dell'encounter, se questo era un encounter } { [7] uses v1.5 format [6] uses v2.1 extended data [5] the item needed must be removed from the group on exit [4] this encounter can be repeated, always (Æ2.2) [3] this encounter can not be repeated, no matter what (Æ2.2) [0] this encounter should not be repeated and already happened (dynamically set in DoEncounterAftermath) } savedWanderingMonstersChance, omm, { Modificatore di morale da applicare al mostro che muove - c' anche mmm che globale } itemNeeded, { Se questo manca, niente incontro } row, j: Integer; quickTimeMovieToBeShown, movingGuyName, { Nome della creatura che muove } arenaName: Str255; { Per ora non ce ne facciamo nulla! } allChars: EntityRef; { For looping characters } XPaward, GPaward: longint; { Num XP da assegnare all'uscita } allDead, { Sono morti tutti? } monstersRobbed, { I mostri depredano i personaggi? } monstersFirst, { Risultato dell'iniziativa } endFormation, { Se true, a posto con la formazione } formation: Boolean; { Se true, non un vero combattimento } nctrHandle: Handle;PROCEDURE RemoveItemNeeded;VAR owner: TPersonaggio; item: TItem;BEGIN owner := NIL; { Cerca ovunque } IF CheckItemNeeded (itemNeeded, owner, neededObjectPlace) THEN BEGIN item := owner.equipaggiamento[neededObjectPlace]; ItemIsThrown (item, owner, TRUE, TRUE) END;END;Function SetUpMonsterPosition (monsterNr, monsterRow: Integer): Point;{ Per ora non in grado di trattare il caso in cui ci sono pi mostri inun gruppo di quanto sia spaziosa l'arena. }VAR pivot: Integer; result: Point;BEGIN WITH result DO BEGIN v := monsterRow; pivot := (arenaX + 1) DIV 2; { Posizione centrale } IF monsterNr <= arenaX DIV 2 THEN { Mostri spaziati, uno ogni due posizioni } IF odd (monsterNr) THEN h := pivot - monsterNr + 1 { Il pivot e a sinistra del pivot } ELSE h := pivot + monsterNr { A destra del pivot } ELSE { Non posso spaziarli uno ogni due, sono troppi. Comincio a tappare i buchi } h := monsterNr - pivot + 1 END; SetUpMonsterPosition := resultEND;Procedure SetUpPosition (who: EntityRef);VAR him: TPersonaggio;BEGIN him := Mondo[who]; him.whereAmI.v := him.formazione.v + arenaY - 3; { formazione ha v = 1..3 } IF him.status[IsDead] THEN BEGIN him.whereAmI.h := -1; { In qs. modo il codice WakeUpLittleSuzie non lo fa muovere } numChars := pred (numChars) END ELSE { L'arena pi piccola accettabile larga 8. Per governare quelle create con vecchie versioni di ScenarioMaker, c' l'IF qui presente } IF arenaX > 7 THEN { Se l'arena larga, mettilo in mezzo } him.whereAmI.h := him.formazione.h + (arenaX - 8) DIV 2 { formazione ha h = 1..8 } ELSE { Se stretta, mettilo da qualche parte valida! } him.whereAmI.h := him.formazione.h MOD arenaX + 1;END;PROCEDURE UnloadArena;BEGIN IF arenaWorld <> NIL THEN DisposeGWorld (arenaWorld); IF arenaWindow <> NIL THEN BEGIN SetPort (mainWindow); { Non lasciamo noi stessi senza grafport valido } HideWindow (arenaWindow); END;END;Procedure LoadArena (ref: INTEGER);VAR arenaHdl: Handle; scanner: Ptr; x, dim, arenaPict: INTEGER; err: OSerr; { Per la costruzione del GWorld con l'arena } arenaLocale: ARRAY [1..maxLocX, 1..maxLocY] of ArenaLocation; { New for v1.7 - workaround to compiler bug } i, j: Integer; arenaBoundsInPixel, r: Rect; oldPort: CGrafPtr; oldGdev: GDHandle; onePoint: Point; theDesktop: RgnHandle;BEGIN { Ref zero significa che l'arena non serve } IF ref = 0 THEN BEGIN arenaWorld := NIL; primoMostro := NIL; Exit (LoadArena); END; { Carica l'arena } arenaHdl := MyGetResource (resArena, ref, TRUE, TRUE); scanner := StripAddress(arenaHdl^); { Interpreta i dati letti } arenaName := GetStringFromRes (scanner); arenaX := GetByteFromres (scanner); arenaY := GetByteFromres (scanner); dim := Sizeof (ArenaLocation) * arenaY; { Dimensione di una colonna di arena locs } IF dim = 0 THEN BEGIN { Arena formato 2.1 } arenaName := GetStringFromRes (scanner); arenaX := GetIntegerFromRes (scanner); arenaY := GetIntegerFromRes (scanner); arenaPict := GetIntegerFromRes (scanner); dim := GetIntegerFromRes (scanner); { spare } dim := Sizeof (ArenaLocation) * arenaY; { Dimensione di una colonna di arena locs } END ELSE arenaPict := 0; for x := 1 to arenaX DO BEGIN BlockMove (scanner, @arenaLocale[x, 1], dim); scanner := Ptr (ORD4 (scanner) + dim) END; HUnlock (arenaHdl); { Ridimensiona la finestra dell'arena } TMSizeWindow (arenaWindow, (arenaX+2)*32, (arenaY+2)*32, TRUE); { Assegna il nome corretto alla finestra } SetWTitle(arenaWindow, arenaName); { Falla apparire } ShowWindow (arenaWindow); TMSelectWindow (arenaWindow); { Se non visibile, spostala - new for v2 } onePoint := arenaWindow^.portRect.botRight; theDesktop := GetGrayRgn; { For sanity checks } LocalToGlobal (onePoint); IF NOT PtInRgn (onePoint, theDesktop) THEN BEGIN onePoint.v := GetMBarHeight + 3; onePoint.h := qd.screenBits.bounds.right - arenaWindow^.portRect.right; { Allineata al bordo dx dello schermo } MyMoveWindow (arenaWindow, onePoint, FALSE); END; { Crea un GWorld con lo sfondo } SetRect(arenaBoundsInPixel,0,0,BSL(arenaX, 5),BSL(arenaY, 5)); err := NewGWorld (arenaWorld, 8 { bit/pixel }, arenaBoundsInPixel, NIL, NIL, 0); IF err <> noErr THEN DeathAlert (errOutOfMemory, err); FixGWorldAndPlotPicture (arenaWorld, arenaPict, arenaWindow); GetGWorld (oldPort, oldGdev); SetGWorld (arenaWorld, NIL); if LockPixels (arenaWorld^.portPixMap) THEN BEGIN EraseRect (arenaWorld^.portRect); IF arenaPict = 0 THEN BEGIN { Arena fatta con le icone } FOR i := 1 TO arenaX DO BEGIN FOR j := 1 TO arenaY DO BEGIN WITH r DO BEGIN right := BSL(i,5); bottom := BSL(j,5); left := right-32; top := bottom-32 END; err := PlotIconID (r, atNone, ttNone, arenaLocale[i, j].icon); END { for j } END { for i } END; { if no picture } UnlockPixels (arenaWorld^.portPixMap); END; { If LockPixels OK } { Copia i dati significativi dall'arena locale alla globale - new for v1.7 } FOR i := 1 TO arenaX DO FOR j := 1 TO arenaY DO arena[i, j] := arenaLocale[i, j].specs; { Ecco fatto. Ora torniamo a bomba } SetGWorld (oldPort, oldGdev);END;PROCEDURE AddMonsterGroup11 (monsterID: Integer; x, y: Byte; tactics: Char; monsterNr: Byte; mdelId: Integer);VAR i: INTEGER;BEGIN FOR i := monsterNr-1 DOWNTO 0 DO BEGIN unMostro := NIL; { Per riconoscere gli out of mem } New (unMostro); FailNIL (unMostro); unMostro.Init (monsterID); { Qs. lo aggiunge anche alla lista } XPaward := XPaward + unMostro.XP; { Tieni conto di quanti XP assegneremo } { Assegnamento di una posizione sulla mappa nella riga row } unMostro.whereAmI.v := y; unMostro.whereAmI.h := x+i; { Assegna il comportamento } unMostro.memory.whom := kNoPCSelected; WITH unMostro.memory DO CASE tactics OF 'P': { Panic } BEGIN action := Panic; unMostro.morale := 0 END; 'F': { Fight } action := Fight; 'G': { Group } action := Group; 'S': { Stand } action := Stand; 'H': { Helpful } action := Friend; END; { case } IF mdelID <> 0 THEN BEGIN unMostro.mdel := MDeLHandle (MyGetResource (resMonsterBrain, mdelID, TRUE, TRUE)); HNoPurge (Handle(unMostro.mdel)); HUnlock (Handle(unMostro.mdel)) END; END; { for }END;PROCEDURE AddMonsterGroup (monsterID, monsterNr: INTEGER);{ Se monsterNr = 0 scopre leggendo la risorsa quanti ce ne sono in un gruppo tipico,poi ne crea quel numero l“ }VAR myMonsterResource: Handle; myMonsterPtr: Ptr; i: INTEGER;BEGIN IF monsterNr = 0 THEN BEGIN { Read from scenario the monster descriptor data if necessary } myMonsterResource := MyGetResource (resMonster, ref, TRUE, TRUE); myMonsterPtr := StripAddress (myMonsterResource^); i := GetByteFromRes (myMonsterPtr); monsterNr := Dado (1, i); { Non faccio HUnlock perchŽ tra poco legger˜ i dati } END; FOR i := 1 TO monsterNr DO BEGIN unMostro := NIL; { Per riconoscere gli out of mem } New (unMostro); FailNIL (unMostro); unMostro.Init (monsterID); { Qs. lo aggiunge anche alla lista } XPaward := XPaward + unMostro.XP; { Tieni conto di quanti XP assegneremo } { Assegnamento di una posizione sulla mappa nella riga row } unMostro.whereAmI := SetUpMonsterPosition (i, row+1); unMostro.memory.action := Fight; unMostro.memory.whom := kNoPCSelected END;END;FUNCTION WakeUpLittleSuzie: Char;{ Chiede a movingGuy che mossa voglia fare. Poi passa al capo. }VAR mossa: Char;BEGIN { Se fuori dall'arena, lasciacelo } IF NOT PtInRect (movingGuy.whereAmI, arenaBounds) THEN WakeUpLittleSuzie := '*' ELSE BEGIN { Evidenziamo il nostro cliente. Potrei lasciarlo fare dall'evento di refresh invalidando il rect, ma cos“ appare subito.} SetPort (arenaWindow); PlotCreatura (movingGuy, ttSelected); REPEAT { Dove vuoi andare, amico? } mossa := movingGuy.Move; UNTIL (mossa <> ' ') & gGameRunning; WakeUpLittleSuzie := mossa END;END;FUNCTION HandleMovement (VAR move: Char; mustCheck: Boolean; VAR pos: Point): Boolean;{ movingGuy si trova in locazione "pos", e ha espresso desideriodi muoversi; processa la mossa.Se mustCheck TRUE, la creatura un personaggio. In questo caso controlla se lamossa ha senso. Se ha senso, restituisci TRUE e metti in pos la nuova locazionedel personaggio. Se ha senso ed una mossa di attacco, cambia "move" in 'A'e riempi la globale attackAdditionalInfo con le specifiche dell'attacco.se non ha senso, restituisci FALSE e lascia il resto invariato.Se mustCheck FALSE, la creatura un mostro. Calcola la nuova posizione,restituisci TRUE ed esci. }VAR newPos: Point; moveMakesSense, staysInArena, isMelee: Boolean; targetMonster: TMostro; PROCEDURE HandleMove (x, y: Integer); BEGIN newPos.h := pos.h + x; newPos.v := pos.v + y END;BEGIN { Si trova in un punto dell'arena che rallenta? } IF arena[pos.h, pos.v, 4] {slows} AND Odd(ora) THEN BEGIN AddToTranscript (movingGuyName, ktIsUnableToMove, '', 0); move := '*'; HandleMovement := TRUE; Exit (HandleMovement) END; { Calcola dove andrebbe se la mossa venisse effettuata } CASE move OF '1': HandleMove (-1, 1); '2': HandleMove ( 0, 1); '3': HandleMove ( 1, 1); '4': HandleMove (-1, 0); '5': HandleMove (0, 0); '6': HandleMove ( 1, 0); '7': HandleMove (-1, -1); '8': HandleMove ( 0, -1); '9': HandleMove ( 1, -1); END; { Per caso sta scappando? } staysInArena := PtInRect (newPos, arenabounds); IF NOT staysInArena THEN BEGIN AddToTranscript (movingGuyName, ktFlees, '', 0); newPos.h := 0; { In futuro sappiamo che non deve muovere } IF not mustCheck THEN BEGIN { Se un mostro } mmm := mmm - 5; { morale } XPaward := XPaward - TMostro (movingGuy).XP; { Non dare i suoi XP } numMonsters := pred (numMonsters) END ELSE BEGIN mmm := mmm + 5; numChars := pred (numChars); IF numChars = 0 THEN GOTO 998; { Force exit from fight } END END; { If flees from arena } { Se un personaggio, controlla che cosa sta facendo. } IF mustCheck THEN BEGIN IF formation THEN BEGIN isMelee := FALSE; moveMakesSense := staysInArena & NOT arena [newPos.h, newPos.v, 7] & NOT CharIsHere (newPos); END ELSE BEGIN isMelee := PosToMonster (newPos, targetMonster); moveMakesSense := isMelee | NOT staysInArena | movingGuy.status[IsFlying] | (NOT arena [newPos.h, newPos.v, 7] & NOT CharIsHere (newPos)); END; IF NOT moveMakesSense THEN DoSoundAsync (sndImpossible) END; { Se un personaggio attacca, scopri chi e come } IF mustCheck & isMelee THEN BEGIN move := 'A'; newPos := pos; { Non un movimento, non mi spostare } WITH attackAdditionalInfo DO BEGIN damage := movingGuy.baseDamage + Dado (movingGuy.dannoInDadi, movingGuy.dimDadi); attackingspell := NIL; IF movingGuy.specialAttacks[WeaponPlusFive] THEN kind := WeaponPlusFive ELSE IF movingGuy.specialAttacks[WeaponPlusFour] THEN kind := WeaponPlusFour ELSE IF movingGuy.specialAttacks[WeaponPlusThree] THEN kind := WeaponPlusThree ELSE IF movingGuy.specialAttacks[WeaponPlusTwo] THEN kind := WeaponPlusTwo ELSE IF movingGuy.specialAttacks[WeaponPlusOne] THEN kind := WeaponPlusOne ELSE kind := Weapon; attackerTHACO := movingGuy.THACO; groundZero := Individual; target := targetMonster; targetRef := kNoPCSelected END; END; { Se una mossa normale, mettila nel transcript } IF staysInArena & (NOT mustCheck | (moveMakesSense & NOT isMelee)) THEN AddToTranscript (movingGuyName, ord(move) - ord ('0'), '', 0); { Qui sarebbe pi sensato fare un IF moveMakesSense, ma il chiamante usa il nuovo valore di pos solo se io ritorno TRUE, quindi va bene cos“ } pos := newPos; { Il tutto ha senso se mossa di un mostro o seÉÊha senso! } HandleMovement := moveMakesSense OR (NOT mustCheck);END;FUNCTION SavingThrow (saver: TCreatura; isCharacter: Boolean): Boolean;VAR STbase: Integer;BEGIN STBase := saver.THACO; IF isCharacter THEN STbase := STbase + ord (TPersonaggio(saver).classe) DIV 2; SavingThrow := Dado (1, 20) > STbaseEND;PROCEDURE DispatchAttack;{ movingGuy sta attaccando. Nella globale attackAdditionalInfo ci sono tutte leinformazioni, debitamente preparate dal codice precedente. Bene, vediamo che effettofa! }VAR effect, video: RgnHandle; effectRect, videoRect: Rect; dirX, dirY, i: Integer; monsterLoop: TMostro; copyOfAai: AttackSpecifier; PROCEDURE IndividualAttack (attack: AttackSpecifier); VAR attackHit: Boolean; targetName: string; aModifierSpell: TIncantesimo; bufferMorale: Longint; { Per evitare gli overflow } Function Hits: Boolean; VAR missed: Boolean; BEGIN missed := Dado (1, 20) < attack.attackerTHACO - attack.target.AC; IF missed THEN BEGIN DoSoundAsync (sndMissed); AddToTranscript (movingGuyName, ktMisses, targetName, 0); Hits := FALSE END ELSE BEGIN attackHit := TRUE; Hits := TRUE END END; BEGIN { IndividualAttack } attackHit := FALSE; { Salvo contrordine supponiamo fallisca } (* * * * * * *) (* *) (* Prima fase *) (* scopro se colpisce *) (* *) (* * * * * * *) WITH attack DO BEGIN targetName := target.nome; { Per i transcript } { Il death magic ha bisogno di un trattamento speciale } IF kind = DeathMagic THEN BEGIN damage := maxint; { Simula un attacco per 32.000 HP } STforNone := TRUE { Bug fix 1.7 } END; { L'attaccato protetto contro questa forma di attacco? } IF target.specialDefenses[kind] THEN BEGIN DoSoundAsync (sndRebound); IF (kind = Missile) OR ((kind >= kFirstWeapon) AND (kind <= kLastWeapon)) THEN BEGIN AddToTranscript ('', ktWeaponRebounded, '', 0); IF gNotificationIsOn THEN GenericDreamAlert (kWeaponFailed) END { if melee } ELSE BEGIN AddToTranscript ('', ktSpellFailed, '', 0); IF gNotificationIsOn THEN GenericDreamAlert (kSpellFailed) END { If spell } END { if has special defenses } ELSE BEGIN { No special defenses. L'attaccato ha una difesa parzale? } IF target.specialModifiers[kind] THEN BEGIN { Si, ma presumibilmente si cucca comunque qualcosa } { Trova l'incantesimo protettivo } aModifierSpell := target.activeSpells; WHILE (aModifierSpell <> NIL) & NOT aModifierSpell.Act (attack) DO aModifierSpell := aModifierSpell.nextSpell; END; { difesa parziale via incantesimo } { Nessuna difesa innata } CASE kind OF kFirstWeapon..kLastWeapon, Missile: IF Hits THEN { Il codice seguente assegna danno }; Poison: IF Hits THEN IF STforNone & SavingThrow (target, targetRef > -1) THEN AddToTranscript (targetName, ktSaves, '', 0) ELSE BEGIN attackHit := TRUE; GenericDreamAlert (kMonsterInjected); AddToTranscript (targetName, ktIsPoisoned, '', 0); target.isPoisoned := succ (target.isPoisoned); END; Illness: IF Hits THEN IF STforNone & SavingThrow (target, targetRef > -1) THEN AddToTranscript (targetName, ktSaves, '', 0) ELSE BEGIN attackHit := TRUE; GenericDreamAlert (kMonsterInjected); AddToTranscript (targetName, ktGetsIll, '', 0); target.status[isIll] := TRUE; END; TurnToStone: IF STforNone & SavingThrow (target, targetRef > -1) THEN AddToTranscript (targetName, ktSaves, '', 0) ELSE IF HPChange (target, -maxint) THEN BEGIN attackHit := TRUE; AddToTranscript (targetName, ktIsStoned, '', 0); KillAllSpells (target); IF target.Kill THEN BEGIN PlotCreatura (target, ttInvisible); { sparisce dal campo } SeppellisciMostro (TMostro(target)); target := NIL END ELSE BEGIN { é un PC } target.status[isDead] := TRUE; target.status[isStoned] := TRUE; { Signal stoning } numChars := pred (numChars); mmm := mmm + 15; CharacterHasChanged (TPersonaggio(target)); IF numChars = 0 THEN GOTO 998; { Force exit from fight } END { PC } END; LevelDrain: { To drain, monster needs a hit } IF Hits THEN BEGIN attackHit := TRUE; GenericDreamAlert (kMonsterDrained); AddToTranscript (targetName, ktIsDrained, '', 0); IF NOT target.status[IsPC] THEN BEGIN { A monster is drained } damage := Dado (1, 8); target.THACO := target.THACO + 1 END ELSE BEGIN { A PC is drained } TPersonaggio(target).livello := pred (TPersonaggio(target).livello); IF TPersonaggio(target).livello > 0 THEN BEGIN { bug fix 1.7 } damage := TPersonaggio(target).baseHP[TPersonaggio(target).livello] + HPBonus (TPersonaggio(target).costituzione, TPersonaggio(target).classe); target.maxHP := target.maxHP - damage; target.THACO := CalcTHACO (TPersonaggio(target).classe, TPersonaggio(target).livello, TPersonaggio(target).livello); IF TPersonaggio(target).maxXP < TPersonaggio(target).XP THEN { é la prima volta che viene drained } TPersonaggio(target).maxXP := TPersonaggio(target).XP; TPersonaggio(target).XPtoNextLevel := FindNextXP (TPersonaggio(target).classe, TPersonaggio(target).livello); TPersonaggio(target).XPForThisLevel := FindNextXP (TPersonaggio(target).classe, TPersonaggio(target).livello-1); END ELSE damage := maxint; { kill him } END { is a PC } END; { is a drain } OTHERWISE BEGIN attackHit := TRUE; { Unless he saves, he takes damage } { Check for saving throws } IF STForHalf & SavingThrow (target, targetRef > kNoPCSelected) THEN BEGIN damage := damage DIV 2; AddToTranscript (targetName, ktSavesforHalf, '', 0); END; IF STforNone THEN BEGIN attackHit := NOT SavingThrow (target, targetRef > kNoPCSelected); IF NOT attackHit THEN BEGIN AddToTranscript (targetName, ktSaves, '', 0); damage := 0 { Non serve, maÉÊ} END; END; { ST for none } END { otherwise - saving throws } END; { case tipo d'attacco of } (* * * * * * *) (* *) (* Seconda fase *) (* se colp“, danneggia! *) (* *) (* * * * * * *) IF attackHit & (target <> NIL) THEN BEGIN { Appiccicagli l'incantesimo } IF attackingSpell <> NIL THEN BEGIN attackingSpell.nextSpell := target.activeSpells; target.activeSpells := attackingSpell END; { Appiccicagli l'incantesimo } { Infliggi danno } IF NOT target.status[IsPC] THEN BEGIN { Colpito un mostro. Abbassagli il morale } bufferMorale := Longint(TMostro (target).morale) - Longint(damage); { Bug fix 1.7 } IF abs(bufferMorale) <= MAXINT THEN TMostro (target).morale := LoWrd(bufferMorale); { Se ti ero amico e mi colpisci sei uno stronzo } IF TMostro (target).memory.action = Friend THEN TMostro (target).memory.action := Fight END { colpito un mostro } ELSE BEGIN { Un mostro ha colpito un PC. Alzagli il morale } bufferMorale := Longint(TMostro (movingGuy).morale) + Longint(damage); { Bug fix 1.7 } IF abs(bufferMorale) <= MAXINT THEN TMostro (movingGuy).morale := LoWrd(bufferMorale); CharacterHasChanged (TPersonaggio(target)); END; IF HPChange (target, -damage) THEN BEGIN { é morto! } KillAllSpells (target); IF target.Kill THEN BEGIN PlotCreatura (target, ttInvisible); { Fallo sparire visivamene } SeppellisciMostro (TMostro(target)); END ELSE BEGIN{ é un PC } numChars := pred (numChars); mmm := mmm + 15; IF numChars = 0 THEN GOTO 998; { Force exit from fight } END { PC } END; { Se ucciso } END { if attack hits } ELSE { Non mi fa niente. Se era un incantesimo, liberatene } IF attackingSpell <> NIL THEN attackingSpell.Free END; { if no special defenses } END { with } END; { attacco individuale } PROCEDURE AddPtToRgn (pt: Point); { Aggiunge il punto dato alla regione correntemente aperta } BEGIN SetRect (effectRect, pt.h, pt.v, pt.h+1, pt.v+1); FrameRect (effectRect) END; PROCEDURE VaiConLaFireball (dove: RgnHandle); { New for v1.3 } CONST rFireball = 133; VAR fireBall: PicHandle; oldClipRgn: RgnHandle; BEGIN fireBall := PicHandle (MyGetResource ('PICT', rFireball, TRUE, FALSE)); { Crea una regione in cui memorizzare la vecchia cliprgn } oldClipRgn := NewRgn; CloseRgn (oldClipRgn); GetClip (oldClipRgn); { Metti la rgn dove la fireball apparirˆ come cliprgn } SetClip(dove); { Disegna la fireball } DrawPicture (fireBall, dove^^.rgnBBox); { Ripristina la situazione iniziale } SetClip (oldClipRgn); DisposeRgn (oldClipRgn); ReleaseResource (Handle (fireBall)) END;BEGIN { Procedura DispatchAttack } { Mo vediamo di che si tratta } WITH attackAdditionalInfo DO IF target = NIL THEN BEGIN SetPort (arenaWindow); { Per l'effetto visivo } { Attacco di area. Innanzitutto calcoliamo l'area di effetto } effect := NewRgn; { Qui ci mettiamo tutti i punti colpiti } CASE groundZero OF StraightBolt: BEGIN { pivot il punto di partenza del bolt. areaWidth la sua lunghezza origin il punto dove si trova il caster } OpenRgn; { Trova la direzione nella quale si muove il bolt } dirX := Signum (pivot.h-origin.h); dirY := Signum (pivot.v-origin.v); { Calcola ciascun punto che fa parte del bolt } FOR i := 1 TO areaWidth DO BEGIN AddPtToRgn (pivot); pivot.v := pivot.v + dirY; pivot.h := pivot.h + dirX END; CloseRgn (effect) END; RoundArea: BEGIN { pivot il centro del cerchio, areaWidth il raggio } OpenRgn; areaWidth := BSR (areaWidth+1, 1); { DIV 2 } SetRect (effectRect, pivot.h-areaWidth, pivot.v-areaWidth, pivot.h+1+areaWidth, pivot.v+1+areaWidth); FrameOval (effectRect); CloseRgn (effect) END; SquareArea: BEGIN { pivot il centro del rettangolo, areaWidth la lunghezza del lato del rect } areaWidth := BSR (areaWidth, 1); { DIV 2 } SetRectRgn(effect, pivot.h-areaWidth, pivot.v-areaWidth, pivot.h+1+areaWidth, pivot.v+1+areaWidth); END; { square } AllMonsters: BEGIN monsterLoop := primoMostro; OpenRgn; WHILE monsterLoop <> NIL DO BEGIN IF DistanzaInMosse (monsterLoop.whereAmI, origin) <= areaWidth THEN BEGIN { Gotcha! } SetRect (effectRect, monsterLoop.whereAmI.h, monsterLoop.whereAmI.v, monsterLoop.whereAmI.h+1, monsterLoop.whereAmI.v+1); FrameRect (effectRect); END; monsterLoop := monsterLoop.nextMonster END; { while } CloseRgn (effect) END; { allMonsters } END; { case } { A questo punto mostra graficamente a video dov' l'area attaccata } video := NewRgn; { Video Effect zoomata a dimensioni video } CloseRgn(video); CopyRgn (effect, video); { Ora bisogna trasformare il rettangolo dal sistema di coordinate al sistema di pixel nel grafPort della arena window } FightCoord2ArenaRect (1, 1, effectRect); FightCoord2ArenaRect (arenaX, arenaY, videoRect); videoRect.topLeft := effectRect.topLeft; { Ora videoRect arenaBounds espresso in pixel } MapRgn (video, arenaBounds, videoRect); { Questo zooma la videoRgn } { New for v 1.3 } CASE kind OF Fire: VaiConLaFireball (video); MagicEnergy: BEGIN ForeColor (yellowColor); PaintRgn (video); END; Acid: PaintRgn (video); { Nero } Electricity: BEGIN ForeColor (cyanColor); PaintRgn (video); END; Frost: BEGIN ForeColor (whiteColor); PaintRgn (video); END; OTHERWISE BEGIN ForeColor (redColor); PaintRgn (video); END; END; { Case } ForeColor (blackColor); InvalRgn (video); { Al prossimo ciclo, cancella il rosso } DisposeRgn (video); { Esegui tutti gli attacchi individuali relativi } monsterLoop := primoMostro; WHILE monsterLoop <> NIL DO BEGIN IF PtInRgn (monsterLoop.whereAmI, effect) THEN BEGIN target := monsterLoop; targetRef := kNoPCSelected; { serve una copia dell'aai, perchŽ i mod spells possono modificarlo } copyOfAai := attackAdditionalInfo; { Qui ci stava un bug tanto carino. La riga seguente era fuori dall'if, e quindi dopo la chiamata a IndividualAttack. Ogni tanto l'incantesimo uccideva il mostro, e cos“ il mostro veniva deallocato, monsterLoop diventava invalido e il ciclo si perdeva nell'iperspazio. } monsterLoop := monsterLoop.nextMonster; IndividualAttack (copyOfAai); END ELSE monsterLoop := monsterLoop.nextMonster END; FOR i := numPC-1 DOWNTO 0 DO IF PtInRgn (Mondo[i].whereAmI, effect) THEN BEGIN target := Mondo[i]; targetRef := i; copyOfAai := attackAdditionalInfo; IndividualAttack (copyOfAai); END; { Liberati della memoria allocata } DisposeRgn (effect); END ELSE { Ricadi nel semplice caso dell'attacco individuale } IndividualAttack (attackAdditionalInfo)END; { proc }PROCEDURE SnoozeUpLittleSuzie (newPos: point);{ Se si spostato, muovi l'icona. Resetta il disegno del campo di battaglia }BEGIN { Se fuori dall'arena, lasciacelo } IF PtInRect (movingGuy.whereAmI, arenaBounds) THEN BEGIN { Invalida l'icona evidenziata (utile anche perchŽ potrebbe essersi mosso) } SetPort (arenaWindow); PlotCreatura (movingGuy, ttInvisible); { Aggiorna la posizione } movingGuy.whereAmI := newPos; { Ridisegnalo nella posizione attuale, ( diversa nel caso in cui si sia mosso) } PlotCreatura (movingGuy, ttNone); END;END;Procedure ExecuteMove (class: TClasse);VAR i: Integer; { Per fare pi di una mossa se ne ha diritto } mossa, copiaDiMossa: Char; moveIsOK: Boolean; targetPos, movingPos: Point; hitRect: rect; item: TItem; { Per manipolare l'arma da lancio } FUNCTION SpellcastIsAllowed: Boolean; { Il personaggio in movingGuy.whereAmI vuol fare un incantesimo. Si pu˜ o la zona lo impedisce? } BEGIN IF arena[movingGuy.whereAmI.h, movingGuy.whereAmI.v, 3] THEN BEGIN AddToTranscript ('', ktSpellFailed, '', 0); SpellcastIsAllowed := FALSE END ELSE SpellcastIsAllowed := TRUE END; PROCEDURE ExecuteThrowingWeapon (thrower: TPersonaggio; weapon: TItem); VAR dmgBase, dmgDiceSize, dmgNumDice: Integer; BEGIN { Servono munizioni? } IF weapon.numCariche = 0 THEN BEGIN { Si, consumane una } moveIsOK := CheckAmmunition (weapon.ID, thrower, TRUE, dmgNumDice, dmgDiceSize, dmgBase); attackAdditionalInfo.damage := dmgBase + Dado (dmgNumDice, dmgDiceSize); END { servono munizioni } ELSE BEGIN moveIsOK := TRUE; attackAdditionalInfo.damage := thrower.baseDamage + Dado (thrower.dannoInDadi, thrower.dimDadi); { No, arma self-propelled. Usane una carica } IF weapon.Use THEN BEGIN { No need to call CharacterHasChanged. ItemIsThrown will invalid the needed places } ItemIsThrown (weapon, thrower, FALSE, FALSE); { New for v1.4: switch to other weapon } IF thrower.equipaggiamento[manoDx].data[6] { is weapon } THEN thrower.wieldedWeapon := manoDx ELSE IF thrower.equipaggiamento[manoSx].data[6] { is weapon } THEN thrower.wieldedWeapon := manoSx; { Signal end of fire } GenericDreamAlert (kAmmoUsedUp); END ELSE { Consumata una carica, ma non finite } ItemHasChanged (weapon); END; { non servono munizioni } { Risolvi l'attacco, tenendo prosente che il raggio d'azione dell'arma si trova in item.specialEffect } WITH attackAdditionalInfo DO BEGIN attackingspell := NIL; kind := Missile; attackerTHACO := thrower.THACO; groundZero := OneMonster; origin := thrower.whereAmI; targetSolved := FALSE END; IF moveIsOK & TargetSystem (TRUE, weapon.specialEffect) THEN BEGIN mossa := 'A'; DoSoundAsync (sndThrownArrow) END ELSE DoSoundAsync (sndImpossible) END;BEGIN { Execute move } { Aggiusta il morale dei mostri } IF class = Mostro THEN BEGIN TMostro (movingGuy).morale := TMostro (movingGuy).morale + omm; { Check di morale } IF (TMostro (movingGuy).memory.action >= Group) & (Dado (1, 100) > TMostro (movingGuy).morale) THEN TMostro (movingGuy).memory.action := Panic END; { Il ciclo fa in modo che chi ha pi di un attacco per round possa effettuarlo. Il problema sta nel dare effettivamente uno-due-uno-due attacchi a chi ha attacchiPerDueRound=3. Me la cavo cos“: trovo se questo un round dispari, e in quasto caso faccio fare (attacchiPerDueRound+1) DIV 2 attacchi, altrimenti attacchiPerDueRound DIV 2 } FOR i := 1 TO (movingGuy.attacchiPerDueRound + ord(odd (ora))) DIV 2 DO BEGIN { Fatti dare la mossa dall'oggetto. Controlla che abbia senso. Se mossa di un giocatore, controlla se si tratta di movimento o di attacco. } moveIsOK := TRUE; lastMove := TickCount; { Start the clock } REPEAT mossa := WakeUpLittleSuzie; copiaDiMossa := mossa; { Per i ladri } movingPos := movingGuy.whereAmI; IF (mossa > '0') & (mossa <= '9') THEN moveIsOK := HandleMovement (mossa, (class <> Mostro), movingPos) ELSE moveIsOK := (mossa IN ['A', '0', 'K', '*', 'F', 'T']); UNTIL moveIsOK; { Se un incantesimo, vediamo di risolverlo usando lo SpellSystem } IF mossa = 'K' THEN If SpellcastIsAllowed & SpellCheck (TRUE) THEN IF TargetSystem (TRUE, attackAdditionalInfo.attackingSpell.range) THEN BEGIN SpellSystem2; IF attackAdditionalInfo.damage <> 0 THEN mossa := 'A'; { Nel seguito debbo processare un attacco } END; { Gestiamo le armi da lancio, finalmente! } IF (mossa = 'T') THEN BEGIN item := TPersonaggio (movingGuy).equipaggiamento[TPersonaggio (movingGuy).wieldedWeapon]; IF item.data[5] { thr. weap.} THEN ExecuteThrowingWeapon (TPersonaggio (movingGuy), item) ELSE DoSoundAsync (sndImpossible) { Non pu˜ lanciare una spada! } END; { if move is 'T' } { Se un attacco, usciamo dal codice precedente con mossa = 'A' } IF mossa = 'A' THEN WITH attackAdditionalInfo DO BEGIN { I ladri fanno doppio danno dalle spalle } IF (class = Ladro) & (copiaDiMossa < '4') & (kind >= kFirstWeapon) & (kind <= kLastWeapon) THEN damage := BSL (damage, 1); IF target <> NIL THEN BEGIN { Segnala il bersaglio } targetPos := target.whereAmI; WITH targetPos DO FightCoord2ArenaRect (h, v, hitRect); SetPort (arenaWindow); ForeColor (redColor); PaintRect (hitRect); ForeColor (blackColor); TMInvalRect (hitRect); END; { Risolvi l'attacco } DispatchAttack END; { Bug fix 1.6: non uscire da un combattimento se l'utente preme F! } IF (formation & (mossa = 'F')) | ((numMonsters = 0) & (NOT formation)) THEN { Sporco trucco per uscire subito. Se questo non viene fatto, finisce il ciclo facendo passare tutti } Goto 998; { OK, rimettilo a nanna. End of story } SnoozeUpLittleSuzie (movingPos) END; { FOR i :=É, ciclo per muovere pi di una volta per round }END;PROCEDURE MonsterTimingSystem (timePassed: Integer);VAR monsterLoop, temp: TMostro; aSpell: TIncantesimo; allChars: Entityref; numDead: Integer;BEGIN { Personaggi, loro equipaggiamento e loro spell } numDead := TimingSystem (timePassed, TRUE); mmm := mmm + numDead * 15; numChars := numChars - numDead; { New for v2.1 } { New for v1.3. Impedisci agli spellcaster fuori dall'arena di usare gli spell } IF Mondo [kNPCReference] <> NIL THEN Mondo[kNPCReference].hitDuringTheRound := TRUE; FOR allChars := numPC-1 DOWNTO 0 DO IF NOT PtInRect (Mondo[allChars].whereAmI, arenaBounds) THEN Mondo[allChars].hitDuringTheRound := TRUE; { Mostri e loro incantesimi } monsterLoop := primoMostro; WHILE (monsterLoop <> NIL) DO BEGIN IF monsterLoop.Time (timePassed) THEN BEGIN { Bug fix 2.1 } temp := monsterLoop.nextMonster; SeppellisciMostro (monsterLoop); monsterLoop := temp END ELSE BEGIN aSpell := monsterLoop.activeSpells; SpellTimingSystem (aSpell, timePassed); monsterLoop.activeSpells := aSpell; monsterLoop := monsterLoop.nextMonster END END;END;PROCEDURE DoEncounterAftermath;VAR shallNotBeRepeated: Boolean;BEGIN IF ref < 999 THEN shallNotBeRepeated := FALSE ELSE IF encounterSpecifics[4] THEN { New for v2.2 - force it to be repeatabl } shallNotBeRepeated := TRUE ELSE IF encounterSpecifics[3] THEN { New for v2.2 - force it to be non-repeatable } shallNotBeRepeated := FALSE ELSE shallNotBeRepeated := placeData [7]; { must be saved on exit } IF shallNotBeRepeated THEN BEGIN LoadResource (nctrHandle); { In case it was purged } SetHandleSize (nctrHandle, 2); { Per salvare spazio nel savegame file } HLock (nctrHandle); nctrHandle^^:= $FFFF; { Encounter done. Next time, skip } DetachResource (nctrHandle); WriteRes (currentSavegameFile, ref, resEncounter, '', nctrHandle); END; ReleaseResource (nctrHandle);END;PROCEDURE DoSetupEncounter;VAR nctrPtr: Ptr; totalRows, j, mdel, resID, x, y, NA: Integer; tact: Char; nctrIsVersion15, nctrIsVersion21: Boolean; { Encounter resource follows new format? }BEGIN nctrHandle := MyGetResource (resEncounter, ref, TRUE, TRUE); nctrPtr := StripAddress(nctrHandle^); { Should encounter be held? } encounterSpecifics := GetBBFromRes (nctrPtr); IF encounterSpecifics[0] THEN Exit (FightingSystem); { Is this a version 1.1, and following, encounter type? } nctrIsVersion15 := encounterSpecifics[7]; nctrIsVersion21 := encounterSpecifics[6]; { Is some item necessary? } itemNeeded := GetIntegerFromRes (nctrPtr); movingGuy := NIL; { It means "Check everybody" - bug fix for v1.6 } IF itemNeeded <> 0 THEN IF NOT CheckItemNeeded (itemNeeded, TPersonaggio(movingGuy), neededObjectPlace) THEN BEGIN HUnlock (nctrHandle); Exit (FightingSystem) END; { L'arena va caricata subito, per sapere dove vanno i mostri. Se il resID zero non ci sono mostri e l'arena non appare } LoadArena (GetIntegerFromRes (nctrPtr)); { Arena res ID } { Il formato della risorsa qui cambia nel caso di Dream 1.1 e successivi. } row := 0; totalRows := GetIntegerFromRes (nctrPtr); IF nctrIsVersion15 THEN WHILE row <= totalRows DO BEGIN resID := GetIntegerFromRes (nctrPtr); x := GetByteFromRes (nctrPtr); y := GetByteFromRes (nctrPtr); tact := Chr (GetByteFromRes (nctrPtr)); na := GetByteFromRes (nctrPtr); mdel := GetIntegerFromRes (nctrPtr); AddMonsterGroup11 (resID, { Monster res ID } x, { X } y, { Y } tact, { Tactics } NA, { nr appearing } mdel); { MDeL ID } row := succ (row) END ELSE WHILE row <= totalRows DO BEGIN resID := GetIntegerFromRes (nctrPtr); na := GetByteFromRes (nctrPtr); AddMonsterGroup (resId, { Monster res ID } na); { nr appearing } j := GetByteFromRes (nctrPtr); { Spare byte } row := succ (row) END; GPaward := GetIntegerFromRes (nctrPtr); { treasure in GP } { Ora carica il tesoro che questo incontro ci riserva. Usiamo a questo scopo la stessa struttura studiata per i negozi, e dettagliata in LowLevel.p } gNumItemsForSell := GetIntegerFromRes (nctrPtr); IF gNumItemsForSell > kMaxItemsInshop THEN DeathAlert (errTooManyItems, gNumItemsForSell); { Bug fix 2.0 } FOR j := 0 TO gNumItemsForSell DO BEGIN ItemID2ItemData (j, GetIntegerFromRes (nctrPtr), FALSE); { Il prezzo in questo caso zeroÉ } itemsForSell[j].itemPrice := 0 END; FOR j := gNumItemsForSell+1 TO kMaxItemsInShop DO itemsForSell[j].iconID := 0; { No more! } { In fondo c' lo ID del testo da far apparire. } j := GetIntegerFromRes (nctrPtr); IF j <> 0 THEN TextOut (j, FALSE); { Infine, il filmato QuickTime. New for v2.1} IF nctrIsVersion21 THEN quickTimeMovieToBeShown := GetStringFromRes (nctrPtr) ELSE quickTimeMovieToBeShown := ''; { No movie } { Grazie e arrivederci } HUnlock (nctrHandle);END;PROCEDURE NotifyOfEarning;VAR okButton: Family; dummy: Integer;BEGIN ClearFamily (okButton); okButton[kStdOkItemIndex] := TRUE; IF gNotificationIsOn THEN BEGIN GetIndString (arenaName, rMiscStrings, rStrMoneyGained); ParamText (arenaName, IntegerToLocalString (GPaward), '', ''); dummy := AlertLord (rBuyNSellAlert, 1, okButton) END;END;BEGIN { Fighting system } { A fighting happened (or an encounter took place). Remember to save! } dirty := TRUE; { Setup variabili } numChars := numPC; { numChars funziona anche da valore di ritorno: se vale zero il chiamante suppone che i PC siano fuggiti } numMonsters := 0; primoMostro := NIL; XPaward := 0; { Num XP che guadagneranno alla fine } IF encounter THEN DoSetupEncounter ELSE BEGIN { Un gruppo di roaming monsters } { Carico l'arena che ha stesso num di risorsa del place! } LoadArena (placeID); { Carico i mostri } row := Dado (1, 2); { In una fila a caso tra la seconda e la terza } AddMonsterGroup (ref, 0); gNumItemsForSell := -1; { Assegna qualche soldo per l'incontro } GPaward := Dado (1, LoWrd (XPaward) DIV 10 + 1); itemNeeded := 0; { Avviene senza condizioni } { removeItemNeededOnExit := FALSE, ovvero } encounterSpecifics[5] := FALSE; END; { Questo codice gestisce anche il setup della formazione. Viene chiamato per un "incontro" fantoccio, definito tra le risorse di Dream DB. In questo caso effettuiamo un paio di azioni speciali, e cio non suoniamo la carica, e non usciamo quando i mostri sono finiti (usciremmo subito) ma bens“ quando l'utente sceglie di nuovo "formazione" nel menu Group } formation := encounter AND (ref = resFormation); endFormation := FALSE; savedWanderingMonstersChance := gPercentWM; gPercentWM := 0; { Noi NON vogliamo che TimingSystem evochi w.m. mentre stiamo giˆ combattendo } { Questo codice potrebbe venire chiamato per smistare un tesoro nascosto. In questo caso evitiamo un po' di lavoroÉ } IF (numMonsters = 0) & NOT formation THEN Goto 1; { Posiziona e prepara i personaggi } FOR allChars := 0 TO numChars-1 DO SetUpPosition (allChars); { Non debbo permettergli di scegliere "formazione" da menu. Se lo facesse avrei due finestre con lo stesso codice operativo, e il main event loop impazzirebbe. NŽ pu˜ uscire dal gioco o eliminare un personaggio in queste condizioni. } IF formation THEN HiliteMenu (0) { Fa sparire la selezione di menu mentre siamo qui } ELSE { Non puoi cambiare formazione mentre combatti } DisableItem (GetMenuHandle (kGroupMenu), kFormation); DisableItem (GetMenuHandle (kCharMenu), kDismiss); DisableItem (GetMenuHandle (kFileMenu), kOpenGame); DisableItem (GetMenuHandle (kFileMenu), kQuit); DisableItem (GetMenuHandle (kFileMenu), kSave); DisableItem (GetMenuHandle (kFileMenu), kSaveAs); { Setup dei mostri } mmm := (numMonsters-numPC)*2; { Aggiustamento di morale } unMostro := primoMostro; WHILE unMostro <> NIL DO BEGIN HLock (Handle (unMostro)); WITH unMostro DO BEGIN { Morale iniziale } morale := morale + mmm; { Disposizione iniziale } IF (allineamento >=CG) & (intelligenza > 12) & NoEvils(FALSE) THEN BEGIN memory.action := Friend; memory.whom := FindAlignedFoe (allineamento); END END; { with } HUnlock (Handle (unMostro)); unMostro := unMostro.nextMonster END; { FOR tutti i mostri } { Questo serve a CanMoveHere e amici } WITH arenaBounds DO BEGIN top := 1; left := 1; right := arenaX+1; bottom := arenaY+1 END; { OK, begin fighting } IF NOT formation THEN BEGIN DoSoundAsync (sndBeginFight); IF gMusicIsOn THEN mmm := QTMusicPlay (137) { 137 = rCombatMusic } END; mmm := 0; { Modificatore di morale maturato nel round precedente } REPEAT { Azzera il coefficiente di aggiustamento del morale, che viene calcolato ad ogni round. } omm := mmm; mmm := 0; { Iniziativa } monstersFirst := Dado (1, 2) = 1; { Iniziativa } { Se i mostri hanno l'inizitiva, muovono } IF monstersFirst THEN BEGIN movingGuy := primoMostro; WHILE movingGuy <> NIL DO BEGIN { Questo servirˆ per i transcript } movingGuyName := movingGuy.nome; ExecuteMove (Mostro); movingGuy := TMostro(movingGuy).nextMonster END { while } END; { if mostri con iniziativa } { I personaggi muovono } FOR allChars := 0 TO numPC-1 DO BEGIN { Queste tre righe fanno s“ che il personaggio che muove sia selezionato nella finestra principale. Ci˜ ha l'effetto secondario di permettere all'utente di richiamare la finestra spell degli spellcaster da menu o semplicemente premendo K } SetPort (mainWindow); gCharSelected := allChars; TMInvalRect (groupRect); movingGuy := Mondo [allChars]; { Questo servirˆ per i transcript } movingGuyName := movingGuy.nome; IF (IsOverloaded (movingGuy.weightLoad, Mondo [allChars].WA) | movingGuy.status[IsIll]) & (Dado (1, 6) > 3) THEN BEGIN DoSoundAsync (sndIll); { Se encumbered o malato } AddToTranscript (movingGuyName, ktIsUnableToMove, '', 0); END ELSE ExecuteMove (Mondo [allChars].classe); END; { Se i mostri non hanno giˆ mosso, muovono ora } IF NOT monstersFirst THEN BEGIN movingGuy := primoMostro; WHILE movingGuy <> NIL DO BEGIN { Questo servirˆ per i transcript } movingGuyName := movingGuy.nome; ExecuteMove (mostro); movingGuy := TMostro(movingGuy).nextMonster END { while } END; { if mostri senza iniziativa } { é passato un round! } MonsterTimingSystem (1); QTMusicIdle; until (((numMonsters = 0) | (numChars = 0)) & NOT formation) | endFormation; 998: IF NOT formation THEN BEGIN DoSoundAsync (sndEndFight); ChooseAndStartTheMusic END; { Uccidi i mostri che sono fuggiti } WHILE primoMostro <> NIL DO SeppellisciMostro (primoMostro); 1: UnloadArena; { Se non sono scappati tutti, dividi il tesoro } IF numChars > 0 THEN BEGIN { New for v2.1 } IF encounterSpecifics[5] THEN RemoveItemNeeded; IF NPCinvolved <> 0 THEN Engine3D_DisposeNPCById (NPCinvolved); { Filmato QuickTime se del caso - new for v2.1 } IF encounter AND gQuickTimeIsOn AND (quickTimeMovieToBeShown <> '') THEN LoadAndShowQuickTime (quickTimeMovieToBeShown); { Innanzitutto i GP } IF GPAward > 0 THEN BEGIN NotifyOfEarning; j := GPAward DIV numPC; FOR allChars := numPC-1 DOWNTO 0 DO BEGIN IF allChars = 0 THEN j := GPAward - j*(numPC-1); { Tutto il resto - bug fix 1.6 } WITH Mondo[allChars] DO BEGIN GP := GP + j; weightLoad := weightLoad + j; movingGuyName := Mondo[allChars].nome END; AddToTranscript (movingGuyName, ktEarns, IToS (j), ktGP); END END; { Poi i punti experienza } XPaward := XPaward + GPaward; { GPs are worth as many XPs } IF XPAward > 0 THEN MoreXP (XPaward DIV numChars); { Dividi i punti esperienza } { Se l'encounter non va ripetuto, segnatelo nel savefile. Questo va fatto PRIMA di chiamare DoLoadPlace per spartire il tesoro, perchŽ altrimenti placedata si riferirebbe al "posto di spartizione", e non all'ambiente dove ho combattuto. } IF encounter THEN DoEncounterAfterMath; { Se ci sono oggetti, permettigli di acciuffarli } IF gNumItemsForSell >= 0 THEN BEGIN DoLoadPlace (rEncounterTreasure, '5'); DoSoundAsync (sndDiscover) END; { se ci sono oggetti } END { se bisogna dividere il tesoro } ELSE BEGIN { Sono fuggiti o morti, gli stupidi. Vediamo un po' } monstersRobbed := FALSE; FOR allChars := numPC-1 DOWNTO 0 DO { Mondo[allChars].whereAmI.h vale zero se morto durante questo combattimento, e -1 se morto in precedenza } IF Mondo[allChars].status[IsDead] AND (Mondo[allChars].whereAmI.h > -1) THEN BEGIN { Se mortoÉ } { Togligli tutti gli oggetti, ma senza fare rumore ogni volta } monstersRobbed := TRUE; FOR neededObjectPlace := Testa TO Sacco6 DO IF Mondo[allChars].equipaggiamento[neededObjectPlace] <> NIL THEN ItemIsThrown (Mondo[allChars].equipaggiamento[neededObjectPlace], Mondo[allChars], TRUE, TRUE); END; { se morto } { se ci sono morti, avvisa che han perso l'equip } IF monstersRobbed THEN GenericDreamAlert (kMonstersRob); { Tutti morti? } allDead := TRUE; FOR allChars := numPC-1 DOWNTO 0 DO IF NOT Mondo[allChars].status[IsDead] THEN allDead := FALSE; IF allDead THEN BEGIN NoChars; { Tutti morti. Pop alla superficie, dove forse troveranno qualcuno che li faccia resuscitare } IF locationStackPointer > 0 THEN BEGIN locationStackPointer := 1; DoLoadPlace (-1, '5') END END; END; IF formation THEN FOR allChars := numPC-1 DOWNTO 0 DO { Salva la posizione dei personaggi in formazione } Mondo[allChars].formazione := Mondo[allChars].whereAmI; { Azzera tutte le posizioni. Questo aiuta SpellSystem ad aggiudicare gli incantesimi peace-time in cui range ed area sono significativi } FOR allChars := numPC-1 DOWNTO 0 DO { Salva la posizione dei personaggi in formazione } Longint (Mondo[allChars].whereAmI) := 0; EnableItem (GetMenuHandle (kGroupMenu), kFormation); EnableItem (GetMenuHandle (kCharMenu), kDismiss); EnableItem (GetMenuHandle (kFileMenu), kOpenGame); EnableItem (GetMenuHandle (kFileMenu), kQuit); EnableItem (GetMenuHandle (kFileMenu), kSave); EnableItem (GetMenuHandle (kFileMenu), kSaveAs); gPercentWM := savedWanderingMonstersChance; { Ripristina eventualitˆ di WM } movingGuy := NILEND; { Procedure }END. { unit }