-
Notifications
You must be signed in to change notification settings - Fork 1
/
SpellSystem.p
668 lines (637 loc) · 22.4 KB
/
SpellSystem.p
1
(********************************************)(* *)(* 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 IN [Caster, 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 } SelectWindow (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 } SelectWindow (mainWindow); SetPort (mainWindow); { Trova il rect dei bersagli } tempRect := groupRect; { é una globale } tempRect.bottom := tempRect.top + numPC*32; { 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 32; 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 } 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; BEGIN 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 BEGIN target.specialModifiers := target.specialModifiers + [attackingSpell.tipo]; CoreSpellSystem := FALSE; Exit (CoreSpellSystem) END; 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: 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 := target.status - [IsIll]; AddToTranscript (unNome, ktIsCured, '', 0); solved := (attackingSpell.codeSelector = 2) END; 3{ inflict }: BEGIN AddToTranscript (unNome, ktGetsIll, '', 0); solved := TRUE; target.status := target.status + [IsIll]; END; END; { case } { 5. Gestione di pietra-carne } TurnToStone: CASE attackingSpell.codeSelector OF 1{ slow }, 2{ cure }: IF IsStoned IN target.status THEN BEGIN target.status := target.status - [IsDead, IsStoned]; AddToTranscript (unNome, ktIsFleshed, '', 0); solved := (attackingSpell.codeSelector = 2) END; 3{ inflict }: BEGIN AddToTranscript (unNome, ktIsStoned, '', 0); solved := TRUE; target.status := target.status + [IsDead, IsStoned]; mmm := mmm + 15; numChars := pred (numChars); 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); InvalRect (mainWindow^.portRect) END; solved := FALSE END; 2: { identify } BEGIN IF (target <> NIL) & (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 := BSR (target.attacchiPerDueRound, 1); AddToTranscript (unNome, ktIsHasted, '', 0); TPersonaggio(target).eta := succ (TPersonaggio (target).eta); solved := FALSE END; { haste } 5: { feign death } IF NOT (IsDead IN target.status) THEN BEGIN target.status := target.status + [IsDead]; 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 (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 isDead IN target.status THEN BEGIN solved := TRUE; { Age for non - humans! } { Constitution survival check } IF (TPersonaggio(target).eta < 80) & (Dado (1, 100) > Progressione (18 - TPersonaggio(target).costituzione)) THEN BEGIN { Did survive } target.status := []; target.isPoisoned := 0; target.HP := 1; AddToTranscript (unNome, ktIsResurrected, '', 0); SetPort (mainWindow); InvalRect (groupRect); END ELSE BEGIN 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 (IsFlying IN target.status) THEN BEGIN target.status := target.status + [IsFlying]; AddToTranscript (unNome, ktFlies, '', 0); END; 1{ ground }: IF IsFlying IN target.status THEN BEGIN AddToTranscript (unNome, ktIsGrounded, '', 0); target.status := target.status - [IsFlying]; END; END; { case } solved := FALSE END; 11: { know alignment } BEGIN ParamText (unNome, OutputAlignment (target.allineamento), '', ''); i := AlertLord (rSpellAlert, 1, [OK]); 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 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; InvalRect (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; FOR loop := Testa TO Sacco6 DO IF TPersonaggio(target).equipaggiamento[loop].data[0] { cursed } THEN ItemIsThrown (TPersonaggio(target).equipaggiamento[loop], TPersonaggio(target), TRUE); END; 17: { Invisibility } IF attackingSpell.reserved = 0 THEN BEGIN { detect } solved := TRUE; cicloMostro := primoMostro; WHILE cicloMostro <> NIL DO BEGIN cicloMostro.status := cicloMostro.status - [IsInvisible]; cicloMostro := cicloMostro.nextMonster END { while } END { detect } ELSE BEGIN { Become } solved := FALSE; target.status := target.status + [IsInvisible]; END; OTHERWISE BEGIN solved := TRUE; ErrorAlert (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 ErrorAlert (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 { Bug fix 1.5.1. Chiamava CharacterHasChanged anche per mostri } IF (target <> NIL) AND (targetRef > kNoPCSelected) 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;