GWorld.pas
上传用户:yj_qiu
上传日期:2022-08-08
资源大小:23636k
文件大小:26k
源码类别:

游戏引擎

开发平台:

Delphi

  1. (*
  2.  Dungeon Looter game world unit
  3.  (C) 2006-2007 George "Mirage" Bakhtadze, mirage@casteng.com
  4.  Unit contains game-specific classes
  5. *)
  6. {$Include GDefines.inc}
  7. {$Include C2Defines.inc}
  8. unit GWorld;
  9. interface
  10. uses
  11.    TextFile,
  12.    Timer,
  13.    BaseTypes, Basics, Props, Base3D, BaseClasses, Collisions, ItemMsg,
  14.    CAST2, C2Visual, C2VisItems, C2Anim, C2Core,
  15.    C2TileMaps,
  16.    DLSound,
  17.    BaseMsg, GameMsg;
  18. const
  19.   DefaultCapacity = 10;              // Default inventory capacity
  20.   TorchLifetime = 10;                // Torch lifetime
  21.   MaxActiveMonsters = 3;             // Max simultaneous monsters
  22.   // Character animation frames
  23.   LastWalkFrame = 9;
  24.   FirstJumpFrame = LastWalkFrame + 1;
  25.   FirstLandFrame = FirstJumpFrame + 6;
  26.   StartPosition: TVector3s = (X: -45.9452786459804; Y: 245.747995633517; Z: -0.5);
  27.   // Gem names, prices and colors
  28.   TotalGemTypes = 4;
  29.   GemNames:  array[0..TotalGemTypes-1] of string[10] = ('Amber    ', 'Emerald ', 'Ruby     ','Diamond');
  30.   GemPrices: array[0..TotalGemTypes-1] of Integer = (150, 250, 500, 1000);
  31.   GemColors: array[0..TotalGemTypes-1] of string[8] = ('FFFFFF00', 'FF00FF00', 'FFFF0000', 'FFFFFFFF');
  32.   // Determines which block can be digged and how many times before they disappear
  33.   DigLevels = 3;
  34.   DiggableBlockIndex: array[0..DigLevels-1] of Integer = (11, 10, 9);
  35.   // Block values on map which represents various map items
  36.   imShop        = TotalGemTypes+1;
  37.   imChestClosed = imShop+1;
  38.   imChestOpened = imChestClosed+1;
  39.   imChestEmpty  = imChestOpened+1;
  40.   // Game item types, names, prices and other conditons
  41.   ItemNames:  array[TGameItem] of string[10] =
  42.    ('Torch    ', 'Small bag ', 'Medium bag', 'Big bag   ', 'Pick      ', 'Lock-pick ',   'Teleport');
  43.   ItemPrices: array[TGameItem] of Integer = (50, 1500, 5000, 10000, 3000, 100, 1000);
  44.   ItemSizes:  array[TGameItem] of Integer = (1,  -10,  -20,  -40,   1,    1,   1);
  45.   ItemGroups: array[TGameItem] of Integer = (0,   -1,   -1,   -1,  -2,    3,   4);     // Only one item per group allowed for negative groups
  46. type
  47.   // Game states
  48.   TGameState = (gsIntro, gsMenu, gsPlay, gsGameOver, gsOutro, gsCredits);
  49.   // Player actions
  50.   TCharacterAction = (caNone, caMoveLeft, caMoveRight, caJump);
  51.   // Jump stages
  52.   TFJumpState      = (jsNone, jsBegin, jsRise, jsLower, jsEnd);
  53.   // Character's inventory
  54.   TInventory = class
  55.   private
  56.     function GetCapacity: Integer;
  57.     function GetOccupied: Integer;
  58.   public
  59.     Gold: Integer;
  60.     Gems:  array[0..TotalGemTypes-1] of Integer;
  61.     Items: array[TGameItem] of Integer;
  62.     property Occupied: Integer read GetOccupied;         // How many space is occupied
  63.     property Capacity: Integer read GetCapacity;
  64.   end;
  65.   // Player character
  66.   TCharacter = class(TMorphedItem)
  67.   private
  68.     // Non-persistent fields
  69.     FDead, FStanding: Boolean;
  70.     FireEmitter: TProcessing;
  71.     TorchLight: TLight;
  72.     // Persistent fields
  73.     InShop: Boolean;
  74.     XSpeed, YSpeed, Angle: Single;
  75.     Direction: Single;                                                // Direction of player - left, right or front
  76.     TorchAge: TTimeUnit;
  77.     AnimFrame: Single;
  78.     FScore: Integer;
  79.     FAction: TCharacterAction;
  80.     FJumpState: TFJumpState;
  81.     JumpTime: TTimeMark;
  82.     JumpStartFrame: Single;
  83.     procedure SetAction(const Value: TCharacterAction);
  84.   public
  85.     Inventory: TInventory;
  86.     constructor Create(AManager: BaseClasses.TItemsManager); override;
  87.     destructor Destroy; override;
  88.     procedure OnSceneLoaded; override;
  89.     procedure OnInit; override;
  90.     procedure OnCollision(Item: TProcessing; const ColRes: Collisions.TCollisionResult); override;
  91.     procedure AddProperties(const Result: Props.TProperties); override;
  92.     procedure SetProperties(Properties: Props.TProperties); override;
  93.     procedure Fire;
  94.     procedure PerformAction;
  95.     procedure PerformTeleport;
  96.     // Character item process
  97.     procedure Process(const DeltaTime: Single); override;
  98.     function IsInLight: Boolean;
  99.     function IsDead: Boolean;
  100.     // Current action
  101.     property Action: TCharacterAction read FAction write SetAction;
  102.     property Score: Integer read FScore;
  103.   end;
  104.   TMonster = class(TPlane)
  105.     Active: Boolean;
  106.     procedure Show; override;
  107.     procedure Hide; override;
  108.     procedure Process(const DeltaTime: Single); override;
  109.   end;
  110.   // Game world
  111.   TGameWorld = class(TProcessing)
  112.   private
  113.     LevelStream: TStream;
  114.     LightSources: TItems;
  115.     TotalLightSources: Integer;
  116.     LastChestTileX, LastChestTileY: Integer;
  117.     procedure DoEmptyChest;
  118.   public
  119.     Sound: TAudiereSound;
  120.     Level: TItem;
  121.     Player: TCharacter;
  122.     Monsters: array of TMonster; ActiveMonsters: Integer;
  123.     SampleMonster: TMonster;
  124.     GodMode: Boolean;               // If true player is invulnerable
  125.     Map: TFgTileMap;                // Main map
  126.     BgMap,                          // Background map
  127.     Gems: TTileMap;                 // Gems, chests and other items map
  128.     constructor Create(AManager: TItemsManager); override;
  129.     destructor Destroy; override;
  130.     procedure Init;
  131.     // Save current level to stream
  132.     procedure SaveLevel(Stream: Basics.TStream);
  133.     // Load current level from stream
  134.     procedure LoadLevel(Stream: Basics.TStream);
  135.     // Initialize level
  136.     procedure InitLevel;
  137.     procedure SaveGame(const FileName: string);
  138.     procedure LoadGame(const FileName: string);
  139.     procedure StartNewGame;
  140.     procedure HandleMessage(const Msg: TMessage); override;
  141.     function IsInLight(const Position: TVector3s): Boolean;
  142.     // Monsters
  143.     procedure AddMonster;
  144.     procedure RemoveMonster(Monster: TMonster);
  145.     procedure Process(const DeltaT: Float); override;
  146.   end;
  147.   // Returns list of classes introduced by the unit
  148.   function GetUnitClassList: TClassArray;
  149. var
  150.   {$IFDEF AUDIO} Audio: TAudioManager; {$ENDIF}
  151.   World: TGameWorld;          
  152.   Core: TCore;                // Alias for Application.Core
  153. implementation
  154. uses SysUtils;
  155. function GetUnitClassList: TClassArray;
  156. begin
  157.   Result := GetClassList([TGameWorld, TCharacter, TMonster]);
  158. end;
  159. { TInventory }
  160. function TInventory.GetCapacity: Integer;
  161. var i: TGameItem;
  162. begin
  163.   Result := DefaultCapacity;
  164.   for i := Low(TGameItem) to High(TGameItem) do if Items[i] > 0 then Inc(Result, MaxI(0, -ItemSizes[i]));
  165. end;
  166. function TInventory.GetOccupied: Integer;
  167. var i: Integer; ii: TGameItem;
  168. begin
  169.   Result := 0;
  170.   for i := 0 to TotalGemTypes-1  do Inc(Result, Gems[i]);
  171.   for ii := Low(TGameItem) to High(TGameItem) do Inc(Result, MaxI(0, Items[ii] * ItemSizes[ii]));
  172. end;
  173. { TGameWorld }
  174. procedure TGameWorld.DoEmptyChest;
  175. var GoldTaken: Integer;
  176. begin
  177.   if LastChestTileX = -1 then Exit;
  178.   Gems.Map[LastChestTileX, LastChestTileY] := imChestEmpty;
  179.   Sound.Play('Gold');
  180.   GoldTaken := 1000 + Core.RandomGen.RndI(101)*10;
  181.   Inc(Player.Inventory.Gold, GoldTaken);
  182.   Inc(Player.FScore, GoldTaken*10);
  183.   LastChestTileX := -1;
  184. end;
  185. constructor TGameWorld.Create(AManager: TItemsManager);
  186. begin
  187.   World := Self;
  188.   inherited;
  189.   Core := FManager as TCore;
  190.   Core.RandomGen.InitSequence(3, 80);             // Init random numbers generator
  191.   LastChestTileX := -1;
  192.   Core.SimultaneousLightSources := 20;            // Many light sources required
  193. end;
  194. destructor TGameWorld.Destroy;
  195. begin
  196.   World := nil;
  197.   FreeAndNil(LevelStream);
  198.   inherited;
  199. end;
  200. procedure TGameWorld.Init;
  201. var i: Integer;
  202. begin
  203.   // Memorize clean level
  204.   Level  := Core.Root.GetChildByName('Level', False);
  205.   LevelStream := TMemoryStream.Create(nil, 0);
  206.   SaveLevel(LevelStream);
  207.   // Clone monsters
  208.   SampleMonster := Core.Root.GetChildByName('Monster', True) as TMonster;
  209.   Assert(Assigned(SampleMonster));
  210.   SetLength(Monsters, 10);
  211.   for i := 0 to High(Monsters) do begin
  212.     Monsters[i] := SampleMonster.Clone as TMonster;
  213.     Monsters[i].Name := 'Monester #' + IntToStr(i);
  214.   end;
  215.   StartNewGame;
  216. end;
  217. procedure TGameWorld.SaveLevel(Stream: TStream);
  218. begin
  219.   Assert(Assigned(Level));
  220.   Level.Save(Stream);
  221. end;
  222. procedure TGameWorld.LoadLevel(Stream: TStream);
  223. var NewLevel: TItem;
  224. begin
  225.   Level.Parent := nil;
  226.   Stream.Seek(0);
  227.   NewLevel := Core.LoadItem(Stream, Core.Root);
  228.   if not Assigned(NewLevel) then begin
  229.     Log.Log('Error loading level', lkError);
  230.     Level.Parent := Core.Root;
  231.     SendMessage(ItemMsg.TSceneLoadedMsg.Create, nil, [mfBroadcast]);
  232.     InitLevel;
  233.     Exit;
  234.   end;
  235.   Level.Free;
  236.   Level := NewLevel;
  237.   SendMessage(ItemMsg.TSceneLoadedMsg.Create, nil, [mfBroadcast]);
  238.   InitLevel;
  239. end;
  240. procedure TGameWorld.InitLevel;
  241. begin
  242.   TotalLightSources := Core.Root.ExtractByClass(TLight, LightSources);
  243.   // Find important items in scene
  244.   Player := Level.GetChildByName('Player',      True) as TCharacter;
  245.   Map    := Level.GetChildByName('TileMapFg',   True) as TFgTileMap;
  246.   BgMap  := Level.GetChildByName('TileMapBg',   True) as TTileMap;
  247.   Gems   := Level.GetChildByName('TileMapGems', True) as TTileMap;
  248. end;
  249. procedure TGameWorld.LoadGame(const FileName: string);
  250. var
  251.   Stream: Basics.TStream;
  252.   Garbage: IRefcountedContainer;
  253. begin
  254.   Stream  := TFileStream.Create(FileName);
  255.   Garbage := CreateRefcountedContainer;
  256.   Garbage.AddObject(Stream);
  257.   LoadLevel(Stream);
  258. end;
  259. procedure TGameWorld.SaveGame(const FileName: string);
  260. var
  261.   Stream: Basics.TStream;
  262.   Garbage: IRefcountedContainer;
  263. begin
  264.   Stream  := TFileStream.Create(FileName);
  265.   Garbage := CreateRefcountedContainer;
  266.   Garbage.AddObject(Stream);
  267.   SaveLevel(Stream);
  268. end;
  269. procedure TGameWorld.StartNewGame;
  270. var i: Integer;
  271. begin
  272.   LoadLevel(LevelStream);
  273.   ActiveMonsters := 0;
  274.   for i := 0 to High(Monsters) do if Monsters[i].Active then Monsters[i].Hide;
  275. end;
  276. function TGameWorld.IsInLight(const Position: TVector3s): Boolean;
  277.   function GetSq(const v: TVector3s): Single;
  278.   begin
  279.     Result := Sqr(v.X) + Sqr(v.Y);
  280.   end;
  281. var i: Integer;
  282. begin
  283.   i := TotalLightSources-1;
  284.   while (i >= 0) and
  285.         (not (isVisible in LightSources[i].State) or
  286.          (GetSq(SubVector3s(TLight(LightSources[i]).GetAbsLocation, Position)) > Sqr(TLight(LightSources[i]).Range))) do
  287.           Dec(i);
  288.   Result := i >= 0;
  289. end;
  290. procedure TGameWorld.AddMonster;
  291. const MonsterAppearRadius = 6;
  292. var i: Integer; Ang: Single;
  293. begin
  294.   i := High(Monsters);
  295.   while (i >= 0) and Monsters[i].Active do Dec(i);
  296.   if i < 0 then Exit;
  297.   Ang := Core.RandomGen.Rnd(2*pi);
  298.   Monsters[i].Position := AddVector3s(Player.GetAbsLocation, GetVector3s(MonsterAppearRadius*Cos(Ang), MonsterAppearRadius*Sin(Ang), Monsters[i].Position.Z));
  299.   if IsInLight(Monsters[i].Position) then Exit;
  300.   Monsters[i].Show;
  301.   Inc(ActiveMonsters);
  302. end;
  303. procedure TGameWorld.RemoveMonster(Monster: TMonster);
  304. var i: Integer;
  305. begin
  306.   i := High(Monsters);
  307.   while (i >= 0) and (Monsters[i] <> Monster) do Dec(i);
  308.   Assert(i >= 0);
  309.   if i < 0 then Exit;  
  310.   Monsters[i].Hide;
  311.   Dec(ActiveMonsters);
  312. end;
  313. procedure TGameWorld.Process(const DeltaT: Float);
  314. begin
  315.   inherited;
  316.   if FManager.EditorMode then Exit;
  317.   if Assigned(Core.Renderer) and Assigned(Player) and (Core.Renderer.MainCamera is TLookAtCamera) then
  318.     TLookAtCamera(Core.Renderer.MainCamera).LookTarget := Player.Position;
  319. //    UpdateHUD;
  320.   if ActiveMonsters < MaxActiveMonsters then AddMonster;
  321. end;
  322. procedure TGameWorld.HandleMessage(const Msg: TMessage);
  323. begin
  324.   inherited;
  325.   // Gameplay messages
  326.   if Msg.ClassType = TGemFoundMsg then with TGemFoundMsg(Msg) do begin
  327.     if Player.Inventory.Occupied < Player.Inventory.Capacity then begin
  328.       Inc(Player.Inventory.Gems[Gems.Map[TileX, TileY]-1]);
  329.       Inc(Player.FScore, GemPrices[Gems.Map[TileX, TileY]-1]*10);
  330.       Gems.Map[TileX, TileY] := 0;
  331.       FManager.HandleMessage(TGemPickedUpMsg.Create(ItemType));
  332.     end;
  333.   end;
  334.   if Msg.ClassType = TEmptyChestMsg then DoEmptyChest;
  335. end;
  336. { TCharacter }
  337. // Some character physics constants
  338. const Gravity = -0.01; HSpeed = 0.075; HSpeedStep = 0.07; AngleStep = 30 * DegToRad;
  339. procedure TCharacter.SetAction(const Value: TCharacterAction);
  340. begin
  341.   case Value of
  342.     caNone: XSpeed := 0;
  343.     caMoveLeft:  if FJumpState <> jsBegin then XSpeed := MaxS(-HSpeed, XSpeed - HSpeedStep);
  344.     caMoveRight: if FJumpState <> jsBegin then XSpeed := MinS( HSpeed, XSpeed + HSpeedStep);
  345.     caJump: if FStanding and (FJumpState = jsNone) then begin
  346.       FJumpState := jsBegin;
  347.       XSpeed := 0;
  348.       JumpStartFrame := AnimFrame;
  349.       Core.Timer.GetInterval(JumpTime, True);
  350.     end;
  351.   end;
  352.   FAction := Value;
  353. end;
  354. constructor TCharacter.Create(AManager: TItemsManager);
  355. begin
  356.   inherited;
  357.   Inventory := TInventory.Create;
  358.   Inventory.Items[giTorch] := 5;
  359.   Inventory.Gold                := 0;
  360.   TorchAge := 0;
  361. end;
  362. destructor TCharacter.Destroy;
  363. begin
  364.   FreeAndNil(Inventory);
  365.   inherited;
  366. end;
  367. procedure TCharacter.OnSceneLoaded;
  368. begin
  369.   inherited;
  370.   OnInit;
  371. end;
  372. procedure TCharacter.OnInit;
  373. begin
  374.   inherited;
  375.   FireEmitter := GetChildByName('FireEmitter', True) as TProcessing;
  376.   TorchLight  := GetChildByName('TorchLight', True) as TLight;
  377. end;
  378. procedure TCharacter.OnCollision(Item: TProcessing; const ColRes: TCollisionResult);
  379. begin
  380.   inherited;
  381.   if FDead or not Assigned(World) then Exit;
  382.   if not World.GodMode and ( Item is TMonster) then begin
  383.     FDead := True;
  384.     FManager.HandleMessage(TGameOverMsg.Create);
  385.   end;
  386. end;
  387. procedure TCharacter.AddProperties(const Result: TProperties);
  388. const CharDataSize = SizeOf(InShop)    + SizeOf(XSpeed)   + SizeOf(YSpeed)    + SizeOf(Angle) +         // ** --- **
  389.                      SizeOf(Direction) + SizeOf(TorchAge) + SizeOf(AnimFrame) + SizeOf(FScore);
  390. begin
  391.   inherited;
  392.   if Assigned(Result) then if not FManager.EditorMode then begin
  393.     // Add persistent fields as a binary data property to force the fields save/load with scene
  394.     Result.AddBinary('GameplayData', [poReadonly, poHidden], @InShop, CharDataSize);
  395.     Result.Add('GameplayAction', vtInt, [{poReadonly, }poHidden], IntToStr(Ord(FAction)), '');
  396.     Result.Add('GameplayScore', vtInt, [{poReadonly, }poHidden], IntToStr(FScore), '');
  397.     Result.Add('InventoryGold', vtInt, [], IntToStr(Inventory.Gold), '');
  398.     // Add getms and items from inventory as binary as well
  399.     Result.AddBinary('InventoryGems',  [poReadonly, poHidden], @Inventory.Gems,  SizeOf(Inventory.Gems));
  400.     Result.AddBinary('InventoryItems', [poReadonly, poHidden], @Inventory.Items, SizeOf(Inventory.Items));
  401.   end;
  402. end;
  403. procedure TCharacter.SetProperties(Properties: TProperties);
  404. const CharDataSize = SizeOf(InShop)    + SizeOf(XSpeed)   + SizeOf(YSpeed)    + SizeOf(Angle) +         // ** --- **
  405.                      SizeOf(Direction) + SizeOf(TorchAge) + SizeOf(AnimFrame) + SizeOf(FScore);
  406. var Data: Pointer;
  407. begin
  408.   inherited;
  409.   Exit;
  410.   if FManager.EditorMode then Exit;
  411.   // Restore persistent fields from binary property
  412.   if Properties.Valid('GameplayData') then begin
  413.     Data := Pointer(Properties.GetAsInteger('GameplayData'));
  414.     if Data <> nil then begin
  415.       Move(Data^, InShop, CharDataSize);
  416.       FreeMem(Data);
  417.     end;
  418.   end;
  419.   if Properties.Valid('GameplayAction') then Action := TCharacterAction(StrToIntDef(Properties['GameplayAction'], 0));
  420.   if Properties.Valid('GameplayScore') then FScore := StrToIntDef(Properties['GameplayScore'], 0);
  421.   if Properties.Valid('InventoryGold') then Inventory.Gold := StrToIntDef(Properties['InventoryGold'], 0);
  422.   // Restore inventory gems from binary property
  423.   if Properties.Valid('InventoryGems') then begin
  424.     Data := Pointer(Properties.GetAsInteger('InventoryGems'));
  425.     if Data <> nil then begin
  426.       Move(Data^, Inventory.Gems, SizeOf(Inventory.Gems));
  427.       FreeMem(Data);
  428.     end;
  429.   end;
  430.   // Restore inventory items from binary property
  431.   if Properties.Valid('InventoryItems') then begin
  432.     Data := Pointer(Properties.GetAsInteger('InventoryItems'));
  433.     if Data <> nil then begin
  434.       Move(Data^, Inventory.Items, SizeOf(Inventory.Items));
  435.       FreeMem(Data);
  436.     end;
  437.   end;
  438. end;
  439. procedure TCharacter.Fire;
  440. begin
  441. end;
  442. procedure TCharacter.PerformAction;
  443. var i, ni, nj: Integer;
  444. begin
  445.   if not Assigned(World) or not Assigned(World.Map) then Exit;
  446.   if FDead then Exit;
  447.   // Check if there is a chest before the character
  448.   if World.Gems.ObtainTileAt(Position.X, Position.Y - Dimensions.Y*0.5, ni, nj) and (World.Gems.Map[ni, nj] = imChestClosed) then begin
  449.     if Inventory.Items[giLockPick] > 0 then begin       // Check for a lockpick
  450.       Dec(Inventory.Items[giLockPick]);
  451.       World.Gems.Map[ni, nj] := imChestOpened;
  452.       World.DoEmptyChest;
  453.       World.LastChestTileX := ni;
  454.       World.LastChestTileY := nj;
  455.       Core.Timer.SetEvent(0.250, TEmptyChestMsg);
  456.       World.Sound.Play('Breakin');
  457.     end else FManager.HandleMessage(TItemLackMsg.Create(giLockPick));
  458.   end else for i := 0 to DigLevels-1 do            // Check if there is a diggable block before the character
  459.     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
  460.       if Inventory.Items[giPick] > 0 then begin
  461.         if i > 0 then begin
  462.           World.Map.Map[ni, nj] := DiggableBlockIndex[i-1];
  463.           World.Sound.Play('Dig');
  464.         end else begin
  465.           World.Map.Map[ni, nj] := 0;
  466.           World.Sound.Play('DigEnd');
  467.         end;
  468.       end else FManager.HandleMessage(TItemLackMsg.Create(giPick));
  469.       Break;
  470.     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
  471.       if Inventory.Items[giPick] > 0 then begin
  472.         if i > 0 then begin
  473.           World.Map.Map[ni, nj] := DiggableBlockIndex[i-1];
  474.           World.Sound.Play('Dig');
  475.         end else begin
  476.           World.Map.Map[ni, nj] := 0;
  477.           World.Sound.Play('DigEnd');
  478.         end;
  479.       end else FManager.HandleMessage(TItemLackMsg.Create(giPick));
  480.       Break;
  481.     end;
  482. end;
  483. procedure TCharacter.Process;
  484. var Bounds: TVector3s; ni, nj: Integer;
  485.   function CanMove(AMap: TTileMap; const MoveVector: TVector3s): Boolean;
  486.     function CanStand(X, Y: Single): Boolean;
  487.     begin
  488.       Result := AMap.ObtainTileAt(X, Y, ni, nj) and (AMap.Map[ni, nj] = 0);
  489.     end;
  490.   var NewLocation: TVector3s;
  491.   begin
  492.     Result := False;
  493.     NewLocation := AddVector3s(Position, MoveVector);
  494.     if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y - Bounds.Y) then Exit;    // -1, -1
  495.     if not CanStand(NewLocation.X           , NewLocation.Y - Bounds.Y) then Exit;    //  0, -1
  496.     if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y - Bounds.Y) then Exit;    //  1, -1
  497.     if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y           ) then Exit;    // -1,  0
  498.     if not CanStand(NewLocation.X           , NewLocation.Y           ) then Exit;    //  0,  0
  499.     if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y           ) then Exit;    //  1,  0
  500.     if not CanStand(NewLocation.X - Bounds.X, NewLocation.Y + Bounds.Y) then Exit;    // -1,  1
  501.     if not CanStand(NewLocation.X           , NewLocation.Y + Bounds.Y) then Exit;    //  0,  1
  502.     if not CanStand(NewLocation.X + Bounds.X, NewLocation.Y + Bounds.Y) then Exit;    //  1,  1
  503.     Result := True;
  504.   end;
  505. var Dist, JumpFrame: Single; JumpDuration: TTimeUnit; LastInShop, OldStanding: Boolean;
  506. begin
  507.   inherited;
  508.   if not Assigned(World) or not Assigned(World.Map) then Exit;
  509.   if FDead then Exit;
  510.   Bounds := Colliding.Volumes[0].Dimensions;
  511.   if CanMove(World.Map, GetVector3s(XSpeed, -Gravity, 0)) then
  512.     Position := AddVector3s(Position, GetVector3s(XSpeed, 0, 0)) else
  513.       XSpeed := 0;
  514.   YSpeed := YSpeed + Gravity;
  515.   // Check if the character is standing or falling
  516.   OldStanding := FStanding;
  517.   FStanding := not CanMove(World.Map, GetVector3s(0, YSpeed, 0));
  518.   if FStanding then begin
  519.     if YSpeed < 0 then
  520.       Dist := World.Map.TraceMap(Position.X, Position.Y - Bounds.Y, 0, YSpeed, ni, nj) else
  521.         Dist := World.Map.TraceMap(Position.X, Position.Y + Bounds.Y, 0, YSpeed, ni, nj);
  522.     Position := AddVector3s(Position, GetVector3s(0, Sign(YSpeed) * (Dist - 0*Abs(Gravity*0.5)), 0));
  523.     FStanding := Sign(YSpeed) = Sign(Gravity);
  524.     YSpeed := 0;
  525.   end else begin
  526.     Position := AddVector3s(Position, GetVector3s(0, YSpeed, 0));
  527.   end;
  528.   if (not OldStanding and FStanding) or (FStanding and (Abs(XSPeed) > epsilon)) then
  529.     World.Sound.Play('Step');
  530.     
  531.   if (not OldStanding and FStanding) and (FJumpState <> jsNone) then begin
  532.     FJumpState := jsEnd;
  533.     JumpStartFrame := AnimFrame;
  534.     Core.Timer.GetInterval(JumpTime, True);
  535.   end;
  536.   // Animate the model
  537.   if FJumpState = jsNone then begin
  538.     if FStanding then AnimFrame := AnimFrame + Abs(XSpeed*6);
  539.     while AnimFrame > LastWalkFrame do AnimFrame := AnimFrame - (LastWalkFrame)+1;
  540.     SetFrames(Trunc(AnimFrame), Trunc(AnimFrame + 1) * (Ord(Trunc(AnimFrame + 1) <= LastWalkFrame) + Ord(Trunc(AnimFrame + 1) <= LastWalkFrame)*0),
  541.               Frac(AnimFrame));
  542.   end else begin
  543.     JumpDuration := Core.Timer.GetInterval(JumpTime, False);
  544. //    if JumpFrame > TotalFrames - LastWalkFrame-1 then JumpFrame := TotalFrames - LastWalkFrame-1;
  545.     if FJumpState = jsEnd then begin                            // Landing
  546.       if (JumpDuration < 0.200)  then
  547.         SetFrames(Trunc(JumpStartFrame), Trunc(FirstLandFrame), Frac(JumpDuration/0.200)) else
  548.           FJumpState := jsNone;
  549.     end else if (FJumpState = jsBegin) and (JumpDuration < 0.200)  then begin                // Jump start
  550.       JumpFrame := JumpDuration/0.100;
  551.       SetFrames(Ord(JumpFrame < 1) * Trunc(JumpStartFrame) + Ord(JumpFrame >= 1) * Trunc(LastWalkFrame+JumpFrame),
  552.               Trunc(LastWalkFrame+JumpFrame+1),
  553.               Frac(JumpFrame));
  554. //      Position := GetVector3s(Position.X, Position.Y - (1-JumpFrame)*0.1, Position.Z);
  555.     end else begin
  556.       if FJumpState = jsBegin then begin
  557.         YSpeed := -Gravity*25.4;
  558.         FJumpState := jsRise;
  559.         Core.Timer.GetInterval(JumpTime, True);
  560.         JumpDuration := 0;
  561.       end;
  562.       JumpFrame := 2+JumpDuration/0.500*2;
  563.       if JumpFrame > TotalFrames - LastWalkFrame-1 then JumpFrame := TotalFrames - LastWalkFrame-1;
  564.       SetFrames(Trunc(LastWalkFrame+JumpFrame),
  565.                 MinI(TotalFrames-1, Trunc(LastWalkFrame+JumpFrame+1)),
  566.                 Frac(JumpFrame));
  567.     end;
  568.   end;
  569.   // Check shop encounter
  570.   LastInShop := InShop;
  571.   InShop     := False;
  572.   if not CanMove(World.Gems, GetVector3s(0, 0, 0)) then begin
  573.     if World.Gems.Map[ni, nj] <= TotalGemTypes then
  574.       World.HandleMessage(TGemFoundMsg.Create(World.Gems.Map[ni, nj], ni, nj)) else
  575.         if World.Gems.Map[ni, nj] = imShop then
  576.           InShop := True;
  577.   end;
  578.   if InShop and not LastInShop then FManager.HandleMessage(TShopOpenMsg.Create);
  579.   if not InShop and LastInShop then FManager.HandleMessage(TShopCloseMsg.Create);
  580.   if XSpeed > epsilon then Direction := -1 else if XSpeed < -epsilon then Direction := 1;
  581.   Angle := MaxS(pi/2, MinS(pi+pi/2, Angle + Direction * AngleStep));
  582. //  if Angle < -pi/2 then Angle := Angle - AngleStep else Angle := -pi/2;
  583.   Orientation := GetQuaternion(Angle, UpVector);
  584.   if Inventory.Items[giTorch] > 0 then begin                    // If the character has torches
  585.     // Turn on particle system emitter and show the particle system to visualize torch
  586.     FireEmitter.State := FireEmitter.State + [isProcessing];
  587.     TorchLight.State  := TorchLight.State  + [isVisible];
  588.     TorchAge := TorchAge + DeltaTime;
  589.     if TorchAge > TorchLifetime then begin
  590.       TorchAge := TorchAge - TorchLifetime;
  591.       Dec(Inventory.Items[giTorch]);
  592.       if Inventory.Items[giTorch] <= 0 then begin
  593.         // Turn off torch
  594.         FireEmitter.State := FireEmitter.State - [isProcessing];
  595.         TorchLight.State  := TorchLight.State  - [isVisible];
  596.       end;
  597.     end;
  598.   end;
  599. end;
  600. function TCharacter.IsInLight: Boolean;
  601. begin
  602.   IsInLight := World.IsInLight(GetAbsLocation);
  603. end;
  604. function TCharacter.IsDead: Boolean;
  605. begin
  606.   Result := FDead;
  607. end;
  608. procedure TCharacter.PerformTeleport;
  609. begin
  610.   if Inventory.Items[giTeleport] > 0 then begin
  611.     Dec(Inventory.Items[giTeleport]);
  612.     Position := StartPosition;
  613.   end else FManager.HandleMessage(TItemLackMsg.Create(giTeleport));  
  614. end;
  615. { TMonster }
  616. procedure TMonster.Hide;
  617. begin
  618.   inherited;
  619.   State := State - [isVisible, isProcessing];
  620.   Active := False;
  621. end;
  622. procedure TMonster.Show;
  623. begin
  624.   inherited;
  625.   Active := True;
  626.   if Assigned(World) and Assigned(World.SampleMonster) then Parent := World.SampleMonster.Parent;
  627.   State := State + [isVisible, isProcessing];
  628. end;
  629. procedure TMonster.Process;
  630. const MonsterSpeed = 0.07;
  631. var Dir: TVector3s;
  632. begin
  633.   inherited;
  634.   if not Assigned(World) then Exit;
  635.   Assert(not(not Active and (isVisible in State)));
  636.   if not Active then Exit;
  637.   if World.IsInLight(GetAbsLocation) then
  638.     World.RemoveMonster(Self);
  639. //  if World.IsInLight(Position) then World.RemoveMonster(Self);
  640.   if Assigned(World.Player) and not World.Player.IsDead and not World.Player.IsInLight then begin
  641.     Dir := SubVector3s(World.Player.GetAbsLocation, GetAbsLocation);
  642. //    Dir.Z := 0;
  643.     NormalizeVector3s(Dir, Dir);
  644.     ScaleVector3s(Dir, Dir, MonsterSpeed);
  645.     Dir := AddVector3s(Position, Dir);
  646. //    if not World.IsInLight(Dir) then
  647.       Position := Dir;
  648.     Position := GetVector3s(Position.X, Position.Y, -1.2);
  649.   end else if Core.RandomGen.RndI(60) = 0 then World.RemoveMonster(Self);
  650. end;
  651. begin
  652.   GlobalClassList.Add('GWorld', GetUnitClassList);
  653. end.