GWorld.pas
资源名称:CAST2SDK.rar [点击查看]
上传用户:yj_qiu
上传日期:2022-08-08
资源大小:23636k
文件大小:26k
源码类别:
游戏引擎
开发平台:
Delphi
- (*
- Dungeon Looter game world unit
- (C) 2006-2007 George "Mirage" Bakhtadze, mirage@casteng.com
- Unit contains game-specific classes
- *)
- {$Include GDefines.inc}
- {$Include C2Defines.inc}
- unit GWorld;
- interface
- uses
- TextFile,
- Timer,
- BaseTypes, Basics, Props, Base3D, BaseClasses, Collisions, ItemMsg,
- CAST2, C2Visual, C2VisItems, C2Anim, C2Core,
- C2TileMaps,
- DLSound,
- BaseMsg, GameMsg;
- const
- DefaultCapacity = 10; // Default inventory capacity
- TorchLifetime = 10; // Torch lifetime
- MaxActiveMonsters = 3; // Max simultaneous monsters
- // Character animation frames
- LastWalkFrame = 9;
- FirstJumpFrame = LastWalkFrame + 1;
- FirstLandFrame = FirstJumpFrame + 6;
- StartPosition: TVector3s = (X: -45.9452786459804; Y: 245.747995633517; Z: -0.5);
- // Gem names, prices and colors
- TotalGemTypes = 4;
- GemNames: array[0..TotalGemTypes-1] of string[10] = ('Amber ', 'Emerald ', 'Ruby ','Diamond');
- GemPrices: array[0..TotalGemTypes-1] of Integer = (150, 250, 500, 1000);
- GemColors: array[0..TotalGemTypes-1] of string[8] = ('FFFFFF00', 'FF00FF00', 'FFFF0000', 'FFFFFFFF');
- // Determines which block can be digged and how many times before they disappear
- DigLevels = 3;
- DiggableBlockIndex: array[0..DigLevels-1] of Integer = (11, 10, 9);
- // Block values on map which represents various map items
- imShop = TotalGemTypes+1;
- imChestClosed = imShop+1;
- imChestOpened = imChestClosed+1;
- imChestEmpty = imChestOpened+1;
- // Game item types, names, prices and other conditons
- ItemNames: array[TGameItem] of string[10] =
- ('Torch ', 'Small bag ', 'Medium bag', 'Big bag ', 'Pick ', 'Lock-pick ', 'Teleport');
- ItemPrices: array[TGameItem] of Integer = (50, 1500, 5000, 10000, 3000, 100, 1000);
- ItemSizes: array[TGameItem] of Integer = (1, -10, -20, -40, 1, 1, 1);
- ItemGroups: array[TGameItem] of Integer = (0, -1, -1, -1, -2, 3, 4); // Only one item per group allowed for negative groups
- type
- // Game states
- TGameState = (gsIntro, gsMenu, gsPlay, gsGameOver, gsOutro, gsCredits);
- // Player actions
- TCharacterAction = (caNone, caMoveLeft, caMoveRight, caJump);
- // Jump stages
- TFJumpState = (jsNone, jsBegin, jsRise, jsLower, jsEnd);
- // Character's inventory
- TInventory = class
- private
- function GetCapacity: Integer;
- function GetOccupied: Integer;
- public
- Gold: Integer;
- Gems: array[0..TotalGemTypes-1] of Integer;
- Items: array[TGameItem] of Integer;
- property Occupied: Integer read GetOccupied; // How many space is occupied
- property Capacity: Integer read GetCapacity;
- end;
- // Player character
- TCharacter = class(TMorphedItem)
- private
- // Non-persistent fields
- FDead, FStanding: Boolean;
- FireEmitter: TProcessing;
- TorchLight: TLight;
- // Persistent fields
- InShop: Boolean;
- XSpeed, YSpeed, Angle: Single;
- Direction: Single; // Direction of player - left, right or front
- TorchAge: TTimeUnit;
- AnimFrame: Single;
- FScore: Integer;
- FAction: TCharacterAction;
- FJumpState: TFJumpState;
- JumpTime: TTimeMark;
- JumpStartFrame: Single;
- procedure SetAction(const Value: TCharacterAction);
- public
- Inventory: TInventory;
- constructor Create(AManager: BaseClasses.TItemsManager); override;
- destructor Destroy; override;
- procedure OnSceneLoaded; override;
- procedure OnInit; override;
- procedure OnCollision(Item: TProcessing; const ColRes: Collisions.TCollisionResult); override;
- procedure AddProperties(const Result: Props.TProperties); override;
- procedure SetProperties(Properties: Props.TProperties); override;
- procedure Fire;
- procedure PerformAction;
- procedure PerformTeleport;
- // Character item process
- procedure Process(const DeltaTime: Single); override;
- function IsInLight: Boolean;
- function IsDead: Boolean;
- // Current action
- property Action: TCharacterAction read FAction write SetAction;
- property Score: Integer read FScore;
- end;
- TMonster = class(TPlane)
- Active: Boolean;
- procedure Show; override;
- procedure Hide; override;
- procedure Process(const DeltaTime: Single); override;
- end;
- // Game world
- TGameWorld = class(TProcessing)
- private
- LevelStream: TStream;
- LightSources: TItems;
- TotalLightSources: Integer;
- LastChestTileX, LastChestTileY: Integer;
- procedure DoEmptyChest;
- public
- Sound: TAudiereSound;
- Level: TItem;
- Player: TCharacter;
- Monsters: array of TMonster; ActiveMonsters: Integer;
- SampleMonster: TMonster;
- GodMode: Boolean; // If true player is invulnerable
- Map: TFgTileMap; // Main map
- BgMap, // Background map
- Gems: TTileMap; // Gems, chests and other items map
- constructor Create(AManager: TItemsManager); override;
- destructor Destroy; override;
- procedure Init;
- // Save current level to stream
- procedure SaveLevel(Stream: Basics.TStream);
- // Load current level from stream
- procedure LoadLevel(Stream: Basics.TStream);
- // Initialize level
- procedure InitLevel;
- procedure SaveGame(const FileName: string);
- procedure LoadGame(const FileName: string);
- procedure StartNewGame;
- procedure HandleMessage(const Msg: TMessage); override;
- function IsInLight(const Position: TVector3s): Boolean;
- // Monsters
- procedure AddMonster;
- procedure RemoveMonster(Monster: TMonster);
- procedure Process(const DeltaT: Float); override;
- end;
- // Returns list of classes introduced by the unit
- function GetUnitClassList: TClassArray;
- var
- {$IFDEF AUDIO} Audio: TAudioManager; {$ENDIF}
- World: TGameWorld;
- Core: TCore; // Alias for Application.Core
- implementation
- uses SysUtils;
- function GetUnitClassList: TClassArray;
- begin
- Result := GetClassList([TGameWorld, TCharacter, TMonster]);
- end;
- { TInventory }
- function TInventory.GetCapacity: Integer;
- var i: TGameItem;
- begin
- Result := DefaultCapacity;
- for i := Low(TGameItem) to High(TGameItem) do if Items[i] > 0 then Inc(Result, MaxI(0, -ItemSizes[i]));
- end;
- function TInventory.GetOccupied: Integer;
- var i: Integer; ii: TGameItem;
- begin
- Result := 0;
- for i := 0 to TotalGemTypes-1 do Inc(Result, Gems[i]);
- for ii := Low(TGameItem) to High(TGameItem) do Inc(Result, MaxI(0, Items[ii] * ItemSizes[ii]));
- end;
- { TGameWorld }
- procedure TGameWorld.DoEmptyChest;
- var GoldTaken: Integer;
- begin
- if LastChestTileX = -1 then Exit;
- Gems.Map[LastChestTileX, LastChestTileY] := imChestEmpty;
- Sound.Play('Gold');
- GoldTaken := 1000 + Core.RandomGen.RndI(101)*10;
- Inc(Player.Inventory.Gold, GoldTaken);
- Inc(Player.FScore, GoldTaken*10);
- LastChestTileX := -1;
- end;
- constructor TGameWorld.Create(AManager: TItemsManager);
- begin
- World := Self;
- inherited;
- Core := FManager as TCore;
- Core.RandomGen.InitSequence(3, 80); // Init random numbers generator
- LastChestTileX := -1;
- Core.SimultaneousLightSources := 20; // Many light sources required
- end;
- destructor TGameWorld.Destroy;
- begin
- World := nil;
- FreeAndNil(LevelStream);
- inherited;
- end;
- procedure TGameWorld.Init;
- var i: Integer;
- begin
- // Memorize clean level
- Level := Core.Root.GetChildByName('Level', False);
- LevelStream := TMemoryStream.Create(nil, 0);
- SaveLevel(LevelStream);
- // Clone monsters
- SampleMonster := Core.Root.GetChildByName('Monster', True) as TMonster;
- Assert(Assigned(SampleMonster));
- SetLength(Monsters, 10);
- for i := 0 to High(Monsters) do begin
- Monsters[i] := SampleMonster.Clone as TMonster;
- Monsters[i].Name := 'Monester #' + IntToStr(i);
- end;
- StartNewGame;
- end;
- procedure TGameWorld.SaveLevel(Stream: TStream);
- begin
- Assert(Assigned(Level));
- Level.Save(Stream);
- end;
- procedure TGameWorld.LoadLevel(Stream: TStream);
- var NewLevel: TItem;
- begin
- Level.Parent := nil;
- Stream.Seek(0);
- NewLevel := Core.LoadItem(Stream, Core.Root);
- if not Assigned(NewLevel) then begin
- Log.Log('Error loading level', lkError);
- Level.Parent := Core.Root;
- SendMessage(ItemMsg.TSceneLoadedMsg.Create, nil, [mfBroadcast]);
- InitLevel;
- Exit;
- end;
- Level.Free;
- Level := NewLevel;
- SendMessage(ItemMsg.TSceneLoadedMsg.Create, nil, [mfBroadcast]);
- InitLevel;
- end;
- procedure TGameWorld.InitLevel;
- begin
- TotalLightSources := Core.Root.ExtractByClass(TLight, LightSources);
- // Find important items in scene
- Player := Level.GetChildByName('Player', True) as TCharacter;
- Map := Level.GetChildByName('TileMapFg', True) as TFgTileMap;
- BgMap := Level.GetChildByName('TileMapBg', True) as TTileMap;
- Gems := Level.GetChildByName('TileMapGems', True) as TTileMap;
- end;
- procedure TGameWorld.LoadGame(const FileName: string);
- var
- Stream: Basics.TStream;
- Garbage: IRefcountedContainer;
- begin
- Stream := TFileStream.Create(FileName);
- Garbage := CreateRefcountedContainer;
- Garbage.AddObject(Stream);
- LoadLevel(Stream);
- end;
- procedure TGameWorld.SaveGame(const FileName: string);
- var
- Stream: Basics.TStream;
- Garbage: IRefcountedContainer;
- begin
- Stream := TFileStream.Create(FileName);
- Garbage := CreateRefcountedContainer;
- Garbage.AddObject(Stream);
- SaveLevel(Stream);
- end;
- procedure TGameWorld.StartNewGame;
- var i: Integer;
- begin
- LoadLevel(LevelStream);
- ActiveMonsters := 0;
- for i := 0 to High(Monsters) do if Monsters[i].Active then Monsters[i].Hide;
- end;
- function TGameWorld.IsInLight(const Position: TVector3s): Boolean;
- function GetSq(const v: TVector3s): Single;
- begin
- Result := Sqr(v.X) + Sqr(v.Y);
- end;
- var i: Integer;
- begin
- i := TotalLightSources-1;
- while (i >= 0) and
- (not (isVisible in LightSources[i].State) or
- (GetSq(SubVector3s(TLight(LightSources[i]).GetAbsLocation, Position)) > Sqr(TLight(LightSources[i]).Range))) do
- Dec(i);
- Result := i >= 0;
- end;
- procedure TGameWorld.AddMonster;
- const MonsterAppearRadius = 6;
- var i: Integer; Ang: Single;
- begin
- i := High(Monsters);
- while (i >= 0) and Monsters[i].Active do Dec(i);
- if i < 0 then Exit;
- Ang := Core.RandomGen.Rnd(2*pi);
- Monsters[i].Position := AddVector3s(Player.GetAbsLocation, GetVector3s(MonsterAppearRadius*Cos(Ang), MonsterAppearRadius*Sin(Ang), Monsters[i].Position.Z));
- if IsInLight(Monsters[i].Position) then Exit;
- Monsters[i].Show;
- Inc(ActiveMonsters);
- end;
- procedure TGameWorld.RemoveMonster(Monster: TMonster);
- var i: Integer;
- begin
- i := High(Monsters);
- while (i >= 0) and (Monsters[i] <> Monster) do Dec(i);
- Assert(i >= 0);
- if i < 0 then Exit;
- Monsters[i].Hide;
- Dec(ActiveMonsters);
- end;
- procedure TGameWorld.Process(const DeltaT: Float);
- begin
- inherited;
- if FManager.EditorMode then Exit;
- if Assigned(Core.Renderer) and Assigned(Player) and (Core.Renderer.MainCamera is TLookAtCamera) then
- TLookAtCamera(Core.Renderer.MainCamera).LookTarget := Player.Position;
- // UpdateHUD;
- if ActiveMonsters < MaxActiveMonsters then AddMonster;
- end;
- procedure TGameWorld.HandleMessage(const Msg: TMessage);
- begin
- inherited;
- // Gameplay messages
- if Msg.ClassType = TGemFoundMsg then with TGemFoundMsg(Msg) do begin
- if Player.Inventory.Occupied < Player.Inventory.Capacity then begin
- Inc(Player.Inventory.Gems[Gems.Map[TileX, TileY]-1]);
- Inc(Player.FScore, GemPrices[Gems.Map[TileX, TileY]-1]*10);
- Gems.Map[TileX, TileY] := 0;
- FManager.HandleMessage(TGemPickedUpMsg.Create(ItemType));
- end;
- end;
- if Msg.ClassType = TEmptyChestMsg then DoEmptyChest;
- end;
- { TCharacter }
- // Some character physics constants
- const Gravity = -0.01; HSpeed = 0.075; HSpeedStep = 0.07; AngleStep = 30 * DegToRad;
- procedure TCharacter.SetAction(const Value: TCharacterAction);
- begin
- case Value of
- caNone: XSpeed := 0;
- caMoveLeft: if FJumpState <> jsBegin then XSpeed := MaxS(-HSpeed, XSpeed - HSpeedStep);
- caMoveRight: if FJumpState <> jsBegin then XSpeed := MinS( HSpeed, XSpeed + HSpeedStep);
- caJump: if FStanding and (FJumpState = jsNone) then begin
- FJumpState := jsBegin;
- XSpeed := 0;
- JumpStartFrame := AnimFrame;
- Core.Timer.GetInterval(JumpTime, True);
- end;
- end;
- FAction := Value;
- end;
- constructor TCharacter.Create(AManager: TItemsManager);
- begin
- inherited;
- Inventory := TInventory.Create;
- Inventory.Items[giTorch] := 5;
- Inventory.Gold := 0;
- TorchAge := 0;
- end;
- destructor TCharacter.Destroy;
- begin
- FreeAndNil(Inventory);
- inherited;
- end;
- procedure TCharacter.OnSceneLoaded;
- begin
- inherited;
- OnInit;
- end;
- procedure TCharacter.OnInit;
- begin
- inherited;
- FireEmitter := GetChildByName('FireEmitter', True) as TProcessing;
- TorchLight := GetChildByName('TorchLight', True) as TLight;
- end;
- procedure TCharacter.OnCollision(Item: TProcessing; const ColRes: TCollisionResult);
- begin
- inherited;
- if FDead or not Assigned(World) then Exit;
- if not World.GodMode and ( Item is TMonster) then begin
- FDead := True;
- FManager.HandleMessage(TGameOverMsg.Create);
- end;
- end;
- procedure TCharacter.AddProperties(const Result: TProperties);
- const CharDataSize = SizeOf(InShop) + SizeOf(XSpeed) + SizeOf(YSpeed) + SizeOf(Angle) + // ** --- **
- SizeOf(Direction) + SizeOf(TorchAge) + SizeOf(AnimFrame) + SizeOf(FScore);
- begin
- inherited;
- if Assigned(Result) then if not FManager.EditorMode then begin
- // Add persistent fields as a binary data property to force the fields save/load with scene
- Result.AddBinary('GameplayData', [poReadonly, poHidden], @InShop, CharDataSize);
- Result.Add('GameplayAction', vtInt, [{poReadonly, }poHidden], IntToStr(Ord(FAction)), '');
- Result.Add('GameplayScore', vtInt, [{poReadonly, }poHidden], IntToStr(FScore), '');
- Result.Add('InventoryGold', vtInt, [], IntToStr(Inventory.Gold), '');
- // Add getms and items from inventory as binary as well
- Result.AddBinary('InventoryGems', [poReadonly, poHidden], @Inventory.Gems, SizeOf(Inventory.Gems));
- Result.AddBinary('InventoryItems', [poReadonly, poHidden], @Inventory.Items, SizeOf(Inventory.Items));
- end;
- end;
- procedure TCharacter.SetProperties(Properties: TProperties);
- const CharDataSize = SizeOf(InShop) + SizeOf(XSpeed) + SizeOf(YSpeed) + SizeOf(Angle) + // ** --- **
- SizeOf(Direction) + SizeOf(TorchAge) + SizeOf(AnimFrame) + SizeOf(FScore);
- var Data: Pointer;
- begin
- inherited;
- Exit;
- if FManager.EditorMode then Exit;
- // Restore persistent fields from binary property
- if Properties.Valid('GameplayData') then begin
- Data := Pointer(Properties.GetAsInteger('GameplayData'));
- if Data <> nil then begin
- Move(Data^, InShop, CharDataSize);
- FreeMem(Data);
- end;
- end;
- if Properties.Valid('GameplayAction') then Action := TCharacterAction(StrToIntDef(Properties['GameplayAction'], 0));
- if Properties.Valid('GameplayScore') then FScore := StrToIntDef(Properties['GameplayScore'], 0);
- if Properties.Valid('InventoryGold') then Inventory.Gold := StrToIntDef(Properties['InventoryGold'], 0);
- // Restore inventory gems from binary property
- if Properties.Valid('InventoryGems') then begin
- Data := Pointer(Properties.GetAsInteger('InventoryGems'));
- if Data <> nil then begin
- Move(Data^, Inventory.Gems, SizeOf(Inventory.Gems));
- FreeMem(Data);
- end;
- end;
- // Restore inventory items from binary property
- if Properties.Valid('InventoryItems') then begin
- Data := Pointer(Properties.GetAsInteger('InventoryItems'));
- if Data <> nil then begin
- Move(Data^, Inventory.Items, SizeOf(Inventory.Items));
- FreeMem(Data);
- end;
- end;
- end;
- procedure TCharacter.Fire;
- begin
- end;
- procedure TCharacter.PerformAction;
- var i, ni, nj: Integer;
- begin
- if not Assigned(World) or not Assigned(World.Map) then Exit;
- if FDead then Exit;
- // Check if there is a chest before the character
- if World.Gems.ObtainTileAt(Position.X, Position.Y - Dimensions.Y*0.5, ni, nj) and (World.Gems.Map[ni, nj] = imChestClosed) then begin
- if Inventory.Items[giLockPick] > 0 then begin // Check for a lockpick
- Dec(Inventory.Items[giLockPick]);
- World.Gems.Map[ni, nj] := imChestOpened;
- World.DoEmptyChest;
- World.LastChestTileX := ni;
- World.LastChestTileY := nj;
- Core.Timer.SetEvent(0.250, TEmptyChestMsg);
- World.Sound.Play('Breakin');
- end else FManager.HandleMessage(TItemLackMsg.Create(giLockPick));
- end else for i := 0 to DigLevels-1 do // Check if there is a diggable block before the character
- if (World.Map.ObtainTileAt(Position.X - Direction * Dimensions.X*2, Position.Y - Dimensions.Y*0.5, ni, nj) and (World.Map.Map[ni, nj] = DiggableBlockIndex[i])) then begin
- if Inventory.Items[giPick] > 0 then begin
- if i > 0 then begin
- World.Map.Map[ni, nj] := DiggableBlockIndex[i-1];
- World.Sound.Play('Dig');
- end else begin
- World.Map.Map[ni, nj] := 0;
- World.Sound.Play('DigEnd');
- end;
- end else FManager.HandleMessage(TItemLackMsg.Create(giPick));
- Break;
- end else if (World.Map.ObtainTileAt(Position.X - Direction * Dimensions.X*2, Position.Y + Dimensions.Y*0.5, ni, nj) and (World.Map.Map[ni, nj] = DiggableBlockIndex[i])) then begin
- if Inventory.Items[giPick] > 0 then begin
- if i > 0 then begin
- World.Map.Map[ni, nj] := DiggableBlockIndex[i-1];
- World.Sound.Play('Dig');
- end else begin
- World.Map.Map[ni, nj] := 0;
- World.Sound.Play('DigEnd');
- end;
- end else FManager.HandleMessage(TItemLackMsg.Create(giPick));
- Break;
- end;
- end;
- procedure TCharacter.Process;
- var Bounds: TVector3s; ni, nj: Integer;
- function CanMove(AMap: TTileMap; const MoveVector: TVector3s): Boolean;
- function CanStand(X, Y: Single): Boolean;
- begin
- Result := AMap.ObtainTileAt(X, Y, ni, nj) and (AMap.Map[ni, nj] = 0);
- end;
- var NewLocation: TVector3s;
- begin
- Result := False;
- NewLocation := AddVector3s(Position, MoveVector);
- if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y - Bounds.Y) then Exit; // -1, -1
- if not CanStand(NewLocation.X , NewLocation.Y - Bounds.Y) then Exit; // 0, -1
- if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y - Bounds.Y) then Exit; // 1, -1
- if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y ) then Exit; // -1, 0
- if not CanStand(NewLocation.X , NewLocation.Y ) then Exit; // 0, 0
- if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y ) then Exit; // 1, 0
- if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y + Bounds.Y) then Exit; // -1, 1
- if not CanStand(NewLocation.X , NewLocation.Y + Bounds.Y) then Exit; // 0, 1
- if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y + Bounds.Y) then Exit; // 1, 1
- Result := True;
- end;
- var Dist, JumpFrame: Single; JumpDuration: TTimeUnit; LastInShop, OldStanding: Boolean;
- begin
- inherited;
- if not Assigned(World) or not Assigned(World.Map) then Exit;
- if FDead then Exit;
- Bounds := Colliding.Volumes[0].Dimensions;
- if CanMove(World.Map, GetVector3s(XSpeed, -Gravity, 0)) then
- Position := AddVector3s(Position, GetVector3s(XSpeed, 0, 0)) else
- XSpeed := 0;
- YSpeed := YSpeed + Gravity;
- // Check if the character is standing or falling
- OldStanding := FStanding;
- FStanding := not CanMove(World.Map, GetVector3s(0, YSpeed, 0));
- if FStanding then begin
- if YSpeed < 0 then
- Dist := World.Map.TraceMap(Position.X, Position.Y - Bounds.Y, 0, YSpeed, ni, nj) else
- Dist := World.Map.TraceMap(Position.X, Position.Y + Bounds.Y, 0, YSpeed, ni, nj);
- Position := AddVector3s(Position, GetVector3s(0, Sign(YSpeed) * (Dist - 0*Abs(Gravity*0.5)), 0));
- FStanding := Sign(YSpeed) = Sign(Gravity);
- YSpeed := 0;
- end else begin
- Position := AddVector3s(Position, GetVector3s(0, YSpeed, 0));
- end;
- if (not OldStanding and FStanding) or (FStanding and (Abs(XSPeed) > epsilon)) then
- World.Sound.Play('Step');
- if (not OldStanding and FStanding) and (FJumpState <> jsNone) then begin
- FJumpState := jsEnd;
- JumpStartFrame := AnimFrame;
- Core.Timer.GetInterval(JumpTime, True);
- end;
- // Animate the model
- if FJumpState = jsNone then begin
- if FStanding then AnimFrame := AnimFrame + Abs(XSpeed*6);
- while AnimFrame > LastWalkFrame do AnimFrame := AnimFrame - (LastWalkFrame)+1;
- SetFrames(Trunc(AnimFrame), Trunc(AnimFrame + 1) * (Ord(Trunc(AnimFrame + 1) <= LastWalkFrame) + Ord(Trunc(AnimFrame + 1) <= LastWalkFrame)*0),
- Frac(AnimFrame));
- end else begin
- JumpDuration := Core.Timer.GetInterval(JumpTime, False);
- // if JumpFrame > TotalFrames - LastWalkFrame-1 then JumpFrame := TotalFrames - LastWalkFrame-1;
- if FJumpState = jsEnd then begin // Landing
- if (JumpDuration < 0.200) then
- SetFrames(Trunc(JumpStartFrame), Trunc(FirstLandFrame), Frac(JumpDuration/0.200)) else
- FJumpState := jsNone;
- end else if (FJumpState = jsBegin) and (JumpDuration < 0.200) then begin // Jump start
- JumpFrame := JumpDuration/0.100;
- SetFrames(Ord(JumpFrame < 1) * Trunc(JumpStartFrame) + Ord(JumpFrame >= 1) * Trunc(LastWalkFrame+JumpFrame),
- Trunc(LastWalkFrame+JumpFrame+1),
- Frac(JumpFrame));
- // Position := GetVector3s(Position.X, Position.Y - (1-JumpFrame)*0.1, Position.Z);
- end else begin
- if FJumpState = jsBegin then begin
- YSpeed := -Gravity*25.4;
- FJumpState := jsRise;
- Core.Timer.GetInterval(JumpTime, True);
- JumpDuration := 0;
- end;
- JumpFrame := 2+JumpDuration/0.500*2;
- if JumpFrame > TotalFrames - LastWalkFrame-1 then JumpFrame := TotalFrames - LastWalkFrame-1;
- SetFrames(Trunc(LastWalkFrame+JumpFrame),
- MinI(TotalFrames-1, Trunc(LastWalkFrame+JumpFrame+1)),
- Frac(JumpFrame));
- end;
- end;
- // Check shop encounter
- LastInShop := InShop;
- InShop := False;
- if not CanMove(World.Gems, GetVector3s(0, 0, 0)) then begin
- if World.Gems.Map[ni, nj] <= TotalGemTypes then
- World.HandleMessage(TGemFoundMsg.Create(World.Gems.Map[ni, nj], ni, nj)) else
- if World.Gems.Map[ni, nj] = imShop then
- InShop := True;
- end;
- if InShop and not LastInShop then FManager.HandleMessage(TShopOpenMsg.Create);
- if not InShop and LastInShop then FManager.HandleMessage(TShopCloseMsg.Create);
- if XSpeed > epsilon then Direction := -1 else if XSpeed < -epsilon then Direction := 1;
- Angle := MaxS(pi/2, MinS(pi+pi/2, Angle + Direction * AngleStep));
- // if Angle < -pi/2 then Angle := Angle - AngleStep else Angle := -pi/2;
- Orientation := GetQuaternion(Angle, UpVector);
- if Inventory.Items[giTorch] > 0 then begin // If the character has torches
- // Turn on particle system emitter and show the particle system to visualize torch
- FireEmitter.State := FireEmitter.State + [isProcessing];
- TorchLight.State := TorchLight.State + [isVisible];
- TorchAge := TorchAge + DeltaTime;
- if TorchAge > TorchLifetime then begin
- TorchAge := TorchAge - TorchLifetime;
- Dec(Inventory.Items[giTorch]);
- if Inventory.Items[giTorch] <= 0 then begin
- // Turn off torch
- FireEmitter.State := FireEmitter.State - [isProcessing];
- TorchLight.State := TorchLight.State - [isVisible];
- end;
- end;
- end;
- end;
- function TCharacter.IsInLight: Boolean;
- begin
- IsInLight := World.IsInLight(GetAbsLocation);
- end;
- function TCharacter.IsDead: Boolean;
- begin
- Result := FDead;
- end;
- procedure TCharacter.PerformTeleport;
- begin
- if Inventory.Items[giTeleport] > 0 then begin
- Dec(Inventory.Items[giTeleport]);
- Position := StartPosition;
- end else FManager.HandleMessage(TItemLackMsg.Create(giTeleport));
- end;
- { TMonster }
- procedure TMonster.Hide;
- begin
- inherited;
- State := State - [isVisible, isProcessing];
- Active := False;
- end;
- procedure TMonster.Show;
- begin
- inherited;
- Active := True;
- if Assigned(World) and Assigned(World.SampleMonster) then Parent := World.SampleMonster.Parent;
- State := State + [isVisible, isProcessing];
- end;
- procedure TMonster.Process;
- const MonsterSpeed = 0.07;
- var Dir: TVector3s;
- begin
- inherited;
- if not Assigned(World) then Exit;
- Assert(not(not Active and (isVisible in State)));
- if not Active then Exit;
- if World.IsInLight(GetAbsLocation) then
- World.RemoveMonster(Self);
- // if World.IsInLight(Position) then World.RemoveMonster(Self);
- if Assigned(World.Player) and not World.Player.IsDead and not World.Player.IsInLight then begin
- Dir := SubVector3s(World.Player.GetAbsLocation, GetAbsLocation);
- // Dir.Z := 0;
- NormalizeVector3s(Dir, Dir);
- ScaleVector3s(Dir, Dir, MonsterSpeed);
- Dir := AddVector3s(Position, Dir);
- // if not World.IsInLight(Dir) then
- Position := Dir;
- Position := GetVector3s(Position.X, Position.Y, -1.2);
- end else if Core.RandomGen.RndI(60) = 0 then World.RemoveMonster(Self);
- end;
- begin
- GlobalClassList.Add('GWorld', GetUnitClassList);
- end.