Merge branch 'master' into states_tier_3

This commit is contained in:
Sezz 2022-09-13 14:01:01 +10:00
commit aaa8213b34
282 changed files with 4887 additions and 3710 deletions

View file

@ -1,6 +1,9 @@
Version 1.0.2 Version 1.0.2
============= =============
* Removing Pistols with TakeItem and SetItemCount now works correctly.
* Vec3s can now be saved and loaded in LevelVars and GameVars.
* Support volume triggers made with node editor.
* Adjust max turn rate of idle state. * Adjust max turn rate of idle state.
* Align Lara on slopes when crouching, crawling, and dying. * Align Lara on slopes when crouching, crawling, and dying.
* Better slope alignment for large, flat enemies (i.e. big scorpion and crocodile). * Better slope alignment for large, flat enemies (i.e. big scorpion and crocodile).
@ -18,8 +21,18 @@ Version 1.0.2
* Fix occasional leave event calls when moving closer to volumes. * Fix occasional leave event calls when moving closer to volumes.
* Fix incorrect viewport size in windowed mode. * Fix incorrect viewport size in windowed mode.
* Fix late landing animation dispatch in rare cases. * Fix late landing animation dispatch in rare cases.
* Fix Lara's subway train death so that she no longer slides slowly after the animation has finished. * Fix Lara's subway train death so that she no longer slides slowly after the animation has finished.
* Fix horseman's axe attack using his left foot as the damaging joint.
* Fix stargate blades needlessly pushing the player around while hardly doing any damage.
Lua API changes:
* Timer.lua, EventSequence.lua and Util.lua have been moved to a subfolder, Engine.
* LevelFuncs can now contain tables as well as functions. These tables can contain functions and other tables, and so forth.
* Moveable functions SetOnHit, SetOnKilled, SetOnCollidedWithObject and SetOnCollidedWithRoom no longer take strings, and instead take function objects themselves.
* EventSequence and Timer no longer require you to call Timer.UpdateAll in OnControlPhase.
* TEN.Logic.AddCallback and TEN.Logic.RemoveCallback have been added.
* GiveItem, TakeItem, and SetItemCount have been reworked (e.g. SetItemCount with a value of -1 can give infinite ammo/consumables).
Version 1.0.1 Version 1.0.1
============= =============

View file

@ -12,7 +12,7 @@ new_type("luautil", "5 Lua utility modules", true)
not_luadoc = true not_luadoc = true
local version = "1.0.1" local version = "1.0.2"
project = "TombEngine" project = "TombEngine"
title = "TombEngine " .. version .. " Lua API" title = "TombEngine " .. version .. " Lua API"
description = "TombEngine " .. version .. " scripting interface" description = "TombEngine " .. version .. " scripting interface"
@ -24,7 +24,7 @@ Other than the "special tables" (GameVars, LevelVars and LevelFuncs), every modu
For example, to call GetMoveableByName, you would have to do: For example, to call GetMoveableByName, you would have to do:
local door = TEN.Objects.GetMoveableByName("door_type4_14") local door = TEN.Objects.GetMoveableByName("door_type4_14")
To save on typing, you can put the following at the start of a Lua file: To save on typing, you can put the following at the start of a Lua file:
local Util = require("Util") local Util = require("Engine.Util")
Util.ShortenTENCalls() Util.ShortenTENCalls()
This will put the modules and classes in the global table. In other words, it means you can do the following: This will put the modules and classes in the global table. In other words, it means you can do the following:
local door = GetMoveableByName("door_type4_14") local door = GetMoveableByName("door_type4_14")

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -511,7 +511,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -102,6 +102,14 @@
<td class="summary">Add a level to the Flow.</td> <td class="summary">Add a level to the Flow.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#GetLevel">GetLevel(id)</a></td>
<td class="summary">GetLevel.</td>
</tr>
<tr>
<td class="name" ><a href="#GetCurrentLevel">GetCurrentLevel()</a></td>
<td class="summary">GetCurrentLevel.</td>
</tr>
<tr>
<td class="name" ><a href="#SetIntroImagePath">SetIntroImagePath(path)</a></td> <td class="name" ><a href="#SetIntroImagePath">SetIntroImagePath(path)</a></td>
<td class="summary">Image to show when loading the game.</td> <td class="summary">Image to show when loading the game.</td>
</tr> </tr>
@ -173,6 +181,55 @@ ambient tracks.
</dd>
<dt>
<a name = "GetLevel"></a>
<strong>GetLevel(id)</strong>
</dt>
<dd>
GetLevel.
Returns the level indicated by the parameter id.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">id</span>
<span class="types"><span class="type">int</span></span>
of the level
</li>
</ul>
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">Level</span></span>
the level indicated by the id
</ol>
</dd>
<dt>
<a name = "GetCurrentLevel"></a>
<strong>GetCurrentLevel()</strong>
</dt>
<dd>
GetCurrentLevel.
Returns the level that the game control is running in that moment.
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">Level</span></span>
the current level
</ol>
</dd> </dd>
<dt> <dt>
<a name = "SetIntroImagePath"></a> <a name = "SetIntroImagePath"></a>
@ -353,7 +410,7 @@ Specify which translations in the strings table correspond to which languages.
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -96,17 +96,21 @@
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#GiveItem">GiveItem(item, count)</a></td> <td class="name" ><a href="#GiveItem">GiveItem(item[, count])</a></td>
<td class="summary">Add x of an item to the inventory.</td> <td class="summary">Add x of an item to the inventory.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#GetItemCount">GetItemCount(item)</a></td> <td class="name" ><a href="#TakeItem">TakeItem(item[, count])</a></td>
<td class="summary">Get the amount the player holds of an item.</td> <td class="summary">Remove x of a certain item from the inventory.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#SetItemCount">SetItemCount(item, count)</a></td> <td class="name" ><a href="#SetItemCount">SetItemCount(item, count)</a></td>
<td class="summary">Set the amount of a certain item the player has in the inventory.</td> <td class="summary">Set the amount of a certain item the player has in the inventory.</td>
</tr> </tr>
<tr>
<td class="name" ><a href="#GetItemCount">GetItemCount(item)</a></td>
<td class="summary">Get the amount the player holds of an item.</td>
</tr>
</table> </table>
<br/> <br/>
@ -118,14 +122,15 @@
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "GiveItem"></a> <a name = "GiveItem"></a>
<strong>GiveItem(item, count)</strong> <strong>GiveItem(item[, count])</strong>
</dt> </dt>
<dd> <dd>
Add x of an item to the inventory. Add x of an item to the inventory.
A count of 0 will add the "default" amount of that item Omitting the second argument will give the "default" amount of the item
(i.e. the amount the player would get from a pickup of that type). (i.e. the amount the player would get from a pickup of that type).
For example, giving "zero" crossbow ammo would give the player For example, giving crossbow ammo without specifying the number would give the player
10 instead, whereas giving "zero" medkits would give the player 1 medkit. 10 instead.
Has no effect if the player has an infinite number of that item.
<h3>Parameters:</h3> <h3>Parameters:</h3>
@ -136,7 +141,62 @@ For example, giving "zero" crossbow ammo would give the player
</li> </li>
<li><span class="parameter">count</span> <li><span class="parameter">count</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
the number of items to add (default: 0) the number of items to add (default: the amount you would get from a pickup)
(<em>optional</em>)
</li>
</ul>
</dd>
<dt>
<a name = "TakeItem"></a>
<strong>TakeItem(item[, count])</strong>
</dt>
<dd>
Remove x of a certain item from the inventory.
As in <a href="../1 modules/Inventory.html#GiveItem">GiveItem</a>, omitting the count will remove the "default" amount of that item.
Has no effect if the player has an infinite number of the item.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">item</span>
<span class="types"><span class="type">InvID</span></span>
the item to be removed
</li>
<li><span class="parameter">count</span>
<span class="types"><span class="type">int</span></span>
the number of items to remove (default: the amount you would get from a pickup)
(<em>optional</em>)
</li>
</ul>
</dd>
<dt>
<a name = "SetItemCount"></a>
<strong>SetItemCount(item, count)</strong>
</dt>
<dd>
Set the amount of a certain item the player has in the inventory.
Similar to <a href="../1 modules/Inventory.html#GiveItem">GiveItem</a> but replaces with the new amount instead of adding it.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">item</span>
<span class="types"><span class="type">InvID</span></span>
the ID of the item to be set.
</li>
<li><span class="parameter">count</span>
<span class="types"><span class="type">int</span></span>
the number of items the player will have. A value of -1 will give an infinite amount of that item.
</li> </li>
</ul> </ul>
@ -165,37 +225,12 @@ For example, giving "zero" crossbow ammo would give the player
<ol> <ol>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
the amount of the item the player has in the inventory the amount of the item the player has in the inventory. -1 indicates an infinite amount of that item.
</ol> </ol>
</dd>
<dt>
<a name = "SetItemCount"></a>
<strong>SetItemCount(item, count)</strong>
</dt>
<dd>
Set the amount of a certain item the player has in the inventory.
Similar to <a href="../1 modules/Inventory.html#GiveItem">GiveItem</a> but replaces with the new amount instead of adding it.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">item</span>
the ID of the item to be set
</li>
<li><span class="parameter">count</span>
<span class="types"><span class="type">int</span></span>
the number of items the player will have
</li>
</ul>
</dd> </dd>
</dl> </dl>
@ -204,7 +239,7 @@ Similar to <a href="../1 modules/Inventory.html#GiveItem">GiveItem</a> but repla
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -32,6 +32,7 @@
<h2>Contents</h2> <h2>Contents</h2>
<ul> <ul>
<li><a href="#Functions">Functions</a></li>
<li><a href="#Special_objects">Special objects </a></li> <li><a href="#Special_objects">Special objects </a></li>
<li><a href="#Special_tables">Special tables </a></li> <li><a href="#Special_tables">Special tables </a></li>
</ul> </ul>
@ -94,6 +95,17 @@
</p> </p>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" ><a href="#AddCallback">AddCallback(CallbackPoint, func)</a></td>
<td class="summary">Register a function as a callback.</td>
</tr>
<tr>
<td class="name" ><a href="#RemoveCallback">RemoveCallback(CallbackPoint, LevelFunc)</a></td>
<td class="summary">Deregister a function as a callback.</td>
</tr>
</table>
<h2><a href="#Special_objects">Special objects </a></h2> <h2><a href="#Special_objects">Special objects </a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
@ -113,7 +125,7 @@
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#LevelFuncs">LevelFuncs</a></td> <td class="name" ><a href="#LevelFuncs">LevelFuncs</a></td>
<td class="summary">A table with level-specific functions.</td> <td class="summary">A table nested table system for level-specific functions.</td>
</tr> </tr>
</table> </table>
@ -121,6 +133,79 @@
<br/> <br/>
<h2 class="section-header "><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "AddCallback"></a>
<strong>AddCallback(CallbackPoint, func)</strong>
</dt>
<dd>
Register a function as a callback.
Possible values for CallbackPoint:</p>
<pre><code>PRECONTROLPHASE -- will be called immediately before OnControlPhase
POSTCONTROLPHASE -- will be called immediately after OnControlPhase
</code></pre>
<p>The order in which two functions with the same CallbackPoint are called is undefined.
i.e. if you register <code>MyFunc</code> and <code>MyFunc2</code> with <code>PRECONTROLPHASE</code>, both will be called before <code>OnControlPhase</code>, but there is no guarantee whether <code>MyFunc</code> will be called before <code>MyFunc2</code>, or vice-versa.</p>
<p>Any returned value will be discarded.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">CallbackPoint</span>
<span class="types"><span class="type">point</span></span>
When should the callback be called?
</li>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
The function to be called (must be in the LevelFuncs hierarchy). Will receive, as an argument, the time in seconds since the last frame.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.MyFunc = <span class="keyword">function</span>(dt) <span class="global">print</span>(dt) <span class="keyword">end</span>
TEN.Logic.AddCallback(TEN.Logic.CallbackPoint.PRECONTROLPHASE, LevelFuncs.MyFunc)</pre>
</ul>
</dd>
<dt>
<a name = "RemoveCallback"></a>
<strong>RemoveCallback(CallbackPoint, LevelFunc)</strong>
</dt>
<dd>
Deregister a function as a callback.
Will have no effect if the function was not registered as a callback
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">CallbackPoint</span>
<span class="types"><span class="type">point</span></span>
The callback point the function was registered with. See <a href="../1 modules/Logic.html#AddCallback">AddCallback</a>
</li>
<li><span class="parameter">LevelFunc</span>
<span class="types"><span class="type">func</span></span>
the function to remove; must be in the LevelFuncs hierarchy.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">TEN.Logic.RemoveCallback(TEN.Logic.CallbackPoint.PRECONTROLPHASE, <span class="string">"MyFunc"</span>)</pre>
</ul>
</dd>
</dl>
<h2 class="section-header "><a name="Special_objects"></a>Special objects </h2> <h2 class="section-header "><a name="Special_objects"></a>Special objects </h2>
<dl class="function"> <dl class="function">
@ -165,7 +250,9 @@ some time later, the values <code>3</code> will be put back into <code>LevelVars
<p><strong>This table is emptied when a level is finished.</strong> If the player needs to be able <p><strong>This table is emptied when a level is finished.</strong> If the player needs to be able
to return to the level (like in the Karnak and Alexandria levels in <em>The Last Revelation</em>), to return to the level (like in the Karnak and Alexandria levels in <em>The Last Revelation</em>),
you will need to use the <a href="../1 modules/Logic.html#GameVars">GameVars</a> table, below. you will need to use the <a href="../1 modules/Logic.html#GameVars">GameVars</a> table, below.</p>
<p><strong>LevelVars.Engine is a reserved table used internally by TombEngine's libs. Do not modify, overwrite, or add to it.</strong>
@ -197,7 +284,9 @@ write:</p>
end end
</code></pre> </code></pre>
<p>Unlike <a href="../1 modules/Logic.html#LevelVars">LevelVars</a>, this table will remain intact for the entirety of the game. <p>Unlike <a href="../1 modules/Logic.html#LevelVars">LevelVars</a>, this table will remain intact for the entirety of the game.</p>
<p><strong>GameVars.Engine is a reserved table used internally by TombEngine's libs. Do not modify, overwrite, or add to it.</strong>
@ -212,9 +301,9 @@ end
</dt> </dt>
<dd> <dd>
<p>A table with level-specific functions. </p> <p>A table nested table system for level-specific functions. </p>
<p>This serves two purposes: it holds the level callbacks (listed below) as well as <p>This serves a few purposes: it holds the level callbacks (listed below) as well as
any trigger functions you might have specified. For example, if you give a trigger any trigger functions you might have specified. For example, if you give a trigger
a Lua name of "my_trigger" in Tomb Editor, you will have to implement it as a member a Lua name of "my_trigger" in Tomb Editor, you will have to implement it as a member
of this table:</p> of this table:</p>
@ -224,6 +313,25 @@ of this table:</p>
end end
</code></pre> </code></pre>
<p>You can organise functions into tables within the hierarchy:</p>
<pre><code>LevelFuncs.enemyFuncs = {}
LevelFuncs.enemyFuncs.makeBaddyRunAway = function()
-- implementation goes here
end
LevelFuncs.enemyFuncs.makeBaddyUseMedkit = function()
-- implementation goes here
end
</code></pre>
<p>There are two special subtables which you should <strong>not</strong> overwrite:</p>
<pre><code>LevelFuncs.Engine -- this is for 'first-party' functions, i.e. ones that come with TombEngine.
LevelFuncs.External -- this is for 'third-party' functions. If you write a library providing LevelFuncs functions for other builders to use in their levels, put those functions in LevelFuncs.External.YourLibraryNameHere
</code></pre>
<p>The following are the level callbacks. They are optional; if your level has no special <p>The following are the level callbacks. They are optional; if your level has no special
behaviour for a particular scenario, you do not need to implement the function. For behaviour for a particular scenario, you do not need to implement the function. For
example, if your level does not need any special initialisation when it is loaded, example, if your level does not need any special initialisation when it is loaded,
@ -279,7 +387,7 @@ and provides the delta time (a float representing game time since last call) via
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -124,6 +124,10 @@
<td class="summary">Set and play an ambient track</td> <td class="summary">Set and play an ambient track</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#PlaySound">PlaySound(sound[, position])</a></td>
<td class="summary">Play sound effect</td>
</tr>
<tr>
<td class="name" ><a href="#CalculateDistance">CalculateDistance(posA, posB)</a></td> <td class="name" ><a href="#CalculateDistance">CalculateDistance(posA, posB)</a></td>
<td class="summary">Calculate the distance between two positions.</td> <td class="summary">Calculate the distance between two positions.</td>
</tr> </tr>
@ -144,10 +148,6 @@
<td class="summary">Vibrate gamepad, if possible.</td> <td class="summary">Vibrate gamepad, if possible.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#PlaySound">PlaySound(sound, position)</a></td>
<td class="summary">Play sound effect</td>
</tr>
<tr>
<td class="name" ><a href="#KeyIsHeld">KeyIsHeld(action)</a></td> <td class="name" ><a href="#KeyIsHeld">KeyIsHeld(action)</a></td>
<td class="summary">Check if particular action key is held</td> <td class="summary">Check if particular action key is held</td>
</tr> </tr>
@ -355,6 +355,32 @@ eyes of the creatures would be.
</dd>
<dt>
<a name = "PlaySound"></a>
<strong>PlaySound(sound[, position])</strong>
</dt>
<dd>
Play sound effect
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">sound</span>
<span class="types"><span class="type">int</span></span>
ID to play. Corresponds to the value in the sound XML file or Tomb Editor's "Sound Infos" window.
</li>
<li><span class="parameter">position</span>
<span class="types"><span class="type">Vec3</span></span>
The 3D position of the sound, i.e. where the sound "comes from". If not given, the sound will not be positional.
(<em>optional</em>)
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "CalculateDistance"></a> <a name = "CalculateDistance"></a>
@ -451,6 +477,17 @@ To be used with <a href="../2 classes/Strings.DisplayString.html#DisplayString:S
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> halfwayX, halfwayY = PercentToScreen(<span class="number">50</span>, <span class="number">50</span>)
<span class="keyword">local</span> baddy
<span class="keyword">local</span> spawnLocationNullmesh = GetMoveableByName(<span class="string">"position_behind_left_pillar"</span>)
<span class="keyword">local</span> str1 = DisplayString(<span class="string">"You spawned a baddy!"</span>, halfwayX, halfwayY, Color(<span class="number">255</span>, <span class="number">100</span>, <span class="number">100</span>), <span class="keyword">false</span>, {DisplayStringOption.SHADOW, DisplayStringOption.CENTER})
LevelFuncs.triggerOne = <span class="keyword">function</span>(obj)
ShowString(str1, <span class="number">4</span>)
<span class="keyword">end</span></pre>
</ul>
</dd> </dd>
<dt> <dt>
@ -514,33 +551,6 @@ To be used with <a href="../2 classes/Strings.DisplayString.html#DisplayString:G
</dd>
<dt>
<a name = "PlaySound"></a>
<strong>PlaySound(sound, position)</strong>
</dt>
<dd>
Play sound effect
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">sound</span>
<span class="types"><span class="type">int</span></span>
ID to play
</li>
<li><span class="parameter">position</span>
<span class="types"><span class="type">Vec3</span></span>
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "KeyIsHeld"></a> <a name = "KeyIsHeld"></a>
@ -633,7 +643,7 @@ To be used with <a href="../2 classes/Strings.DisplayString.html#DisplayString:G
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-08 19:16:14 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -297,7 +297,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -169,7 +169,7 @@ with a call to <a href="../1 modules/Strings.html#ShowString">ShowString</a>, or
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -100,7 +100,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -112,7 +112,7 @@
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#Fog.new">Fog.new(color, Min, Max)</a></td> <td class="name" ><a href="#Fog">Fog(color, Min, Max)</a></td>
<td class="summary"> <td class="summary">
</td> </td>
@ -185,8 +185,8 @@
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "Fog.new"></a> <a name = "Fog"></a>
<strong>Fog.new(color, Min, Max)</strong> <strong>Fog(color, Min, Max)</strong>
</dt> </dt>
<dd> <dd>
@ -227,7 +227,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -182,7 +182,7 @@ EXAMINE
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -120,8 +120,7 @@
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#layer2">layer2</a></td> <td class="name" ><a href="#layer2">layer2</a></td>
<td class="summary">(<a href="../2 classes/Flow.SkyLayer.html#">Flow.SkyLayer</a>) Secondary sky layer <td class="summary">(<a href="../2 classes/Flow.SkyLayer.html#">Flow.SkyLayer</a>) Secondary sky layer</td>
<strong>(not yet implemented)</strong></td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#fog">fog</a></td> <td class="name" ><a href="#fog">fog</a></td>
@ -132,10 +131,6 @@
<td class="summary">(bool) Draw sky layer?</td> <td class="summary">(bool) Draw sky layer?</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#colAddHorizon">colAddHorizon</a></td>
<td class="summary">(bool) Enable smooth transition from horizon graphic to sky layer.</td>
</tr>
<tr>
<td class="name" ><a href="#storm">storm</a></td> <td class="name" ><a href="#storm">storm</a></td>
<td class="summary">(bool) Enable flickering lightning in the sky.</td> <td class="summary">(bool) Enable flickering lightning in the sky.</td>
</tr> </tr>
@ -175,7 +170,7 @@
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#Level.new">Level.new()</a></td> <td class="name" ><a href="#Level">Level()</a></td>
<td class="summary">Make a new Level object.</td> <td class="summary">Make a new Level object.</td>
</tr> </tr>
</table> </table>
@ -282,7 +277,6 @@
</dt> </dt>
<dd> <dd>
(<a href="../2 classes/Flow.SkyLayer.html#">Flow.SkyLayer</a>) Secondary sky layer (<a href="../2 classes/Flow.SkyLayer.html#">Flow.SkyLayer</a>) Secondary sky layer
<strong>(not yet implemented)</strong>
@ -320,23 +314,6 @@
</dd>
<dt>
<a name = "colAddHorizon"></a>
<strong>colAddHorizon</strong>
</dt>
<dd>
(bool) Enable smooth transition from horizon graphic to sky layer.
If set to false, there will be a black band between the two.</p>
<p> <strong>(not yet implemented)</strong>
</dd> </dd>
<dt> <dt>
<a name = "storm"></a> <a name = "storm"></a>
@ -494,8 +471,8 @@ Must be at least 4.</p>
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "Level.new"></a> <a name = "Level"></a>
<strong>Level.new()</strong> <strong>Level()</strong>
</dt> </dt>
<dd> <dd>
Make a new Level object. Make a new Level object.
@ -519,7 +496,7 @@ Must be at least 4.</p>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -100,7 +100,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -115,19 +115,19 @@
<dd> <dd>
How should the application respond to script errors? How should the application respond to script errors?
Must be one of the following: Must be one of the following:
<code>ErrorMode.TERMINATE</code> - print to the log file and terminate the application when any script error is hit. <code>ErrorMode.TERMINATE</code> - print to the log file and return to the title level when any script error is hit.
This is the one you will want to go for if you want to know IMMEDIATELY if something has gone wrong.</p> This is the one you will want to go for if you want to know IMMEDIATELY if something has gone wrong.</p>
<p><code>ErrorMode.WARN</code> - print to the log file and continue running the application when a recoverable script error is hit. <p><code>ErrorMode.WARN</code> - print to the log file and continue running the application when a recoverable script error is hit.
Choose this one if terminating the application is too much for you. Note that unrecoverable errors will still terminate Choose this one if booting to the title level is too much for you.</p>
the application.</p>
<p><code>ErrorMode.SILENT</code> - do nothing when a recoverable script error is hit. <p><code>ErrorMode.SILENT</code> - do nothing when a recoverable script error is hit.
Think <strong>very</strong> carefully before using this setting. These error modes are here to help you to keep your scripts Think <strong>very</strong> carefully before using this setting. These error modes are here to help you to keep your scripts
working properly, but if you opt to ignore errors, you won't be alerted if you've misused a function or passed working properly, but if you opt to ignore errors, you won't be alerted if you've misused a function or passed
an invalid argument.</p> an invalid argument.</p>
<p>As with <code>ErrorMode.WARN</code>, unrecoverable errors will still terminate the application. <p>In all of these modes, an <em>unrecoverable</em> error will boot you to the title level. If the title level itself
has an unrecoverable error, the game will close.
@ -143,7 +143,7 @@ an invalid argument.</p>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -106,7 +106,7 @@
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#SkyLayer.new">SkyLayer.new(color, speed)</a></td> <td class="name" ><a href="#SkyLayer">SkyLayer(color, speed)</a></td>
<td class="summary"> <td class="summary">
</td> </td>
@ -161,8 +161,8 @@ Less is more. City of The Dead, for example, uses a speed value of 16.
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "SkyLayer.new"></a> <a name = "SkyLayer"></a>
<strong>SkyLayer.new(color, speed)</strong> <strong>SkyLayer(color, speed)</strong>
</dt> </dt>
<dd> <dd>
@ -199,7 +199,7 @@ Less is more. City of The Dead, for example, uses a speed value of 16.
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -372,7 +372,7 @@ aiObj:SetObjectID(TEN.Objects.ObjID.AI_PATROL1)</pre>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -260,7 +260,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -95,7 +95,7 @@ pickups, and Lara herself.</p>
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#Moveable">Moveable(object, name, position, rotation, room, animNumber, frameNumber, hp, OCB, AIBits)</a></td> <td class="name" ><a href="#Moveable">Moveable(object, name, position[, rotation[, room[, animNumber=0[, frameNumber=0[, hp=10[, OCB=0[, AIBits]]]]]]])</a></td>
<td class="summary">For more information on each parameter, see the <td class="summary">For more information on each parameter, see the
associated getters and setters.</td> associated getters and setters.</td>
</tr> </tr>
@ -124,39 +124,6 @@ associated getters and setters.</td>
<td class="summary">Get the status of object.</td> <td class="summary">Get the status of object.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Moveable:SetOnHit">Moveable:SetOnHit(name)</a></td>
<td class="summary">Set the name of the function to be called when the moveable is shot by Lara
Note that this will be triggered twice when shot with both pistols at once.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetOnHit">Moveable:GetOnHit()</a></td>
<td class="summary">Get the name of the function called when this moveable is shot</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithObject">Moveable:SetOnCollidedWithObject(name)</a></td>
<td class="summary">Set the name of the function called when this moveable collides with another moveable</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetOnCollidedWithObject">Moveable:GetOnCollidedWithObject()</a></td>
<td class="summary">Get the name of the function called when this moveable collides with another moveable</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithRoom">Moveable:SetOnCollidedWithRoom(name)</a></td>
<td class="summary">Set the name of the function called when this moveable collides with room geometry (e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetOnCollidedWithRoom">Moveable:GetOnCollidedWithRoom()</a></td>
<td class="summary">Get the name of the function called when this moveable collides with room geometry (e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnKilled">Moveable:SetOnKilled(callback)</a></td>
<td class="summary">Set the name of the function to be called when the moveable is destroyed/killed</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetOnKilled">Moveable:GetOnKilled()</a></td>
<td class="summary">Get the name of the function called when this moveable is killed</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetObjectID">Moveable:GetObjectID()</a></td> <td class="name" ><a href="#Moveable:GetObjectID">Moveable:GetObjectID()</a></td>
<td class="summary">Retrieve the object ID</td> <td class="summary">Retrieve the object ID</td>
</tr> </tr>
@ -197,6 +164,10 @@ associated getters and setters.</td>
<td class="summary">Set current HP (hit points/health points)</td> <td class="summary">Set current HP (hit points/health points)</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Moveable:GetSlotHP">Moveable:GetSlotHP(ID)</a></td>
<td class="summary">Get HP definded for that object type (hit points/health points) (Read Only).</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetOCB">Moveable:GetOCB()</a></td> <td class="name" ><a href="#Moveable:GetOCB">Moveable:GetOCB()</a></td>
<td class="summary">Get OCB (object code bit) of the moveable</td> <td class="summary">Get OCB (object code bit) of the moveable</td>
</tr> </tr>
@ -205,6 +176,14 @@ associated getters and setters.</td>
<td class="summary">Set OCB (object code bit) of the moveable</td> <td class="summary">Set OCB (object code bit) of the moveable</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Moveable:GetItemFlags">Moveable:GetItemFlags()</a></td>
<td class="summary">Get the value stored in ItemFlags[x] (x is the value of the parameter)</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetItemFlags">Moveable:SetItemFlags(value)</a></td>
<td class="summary">Stores the value of the first parameter in the ItemFlags[x] (x is the value of the second parameter)</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetColor">Moveable:GetColor()</a></td> <td class="name" ><a href="#Moveable:GetColor">Moveable:GetColor()</a></td>
<td class="summary">Get the moveable's color</td> <td class="summary">Get the moveable's color</td>
</tr> </tr>
@ -270,27 +249,8 @@ associated getters and setters.</td>
<td class="summary">Determine whether the moveable is active or not</td> <td class="summary">Determine whether the moveable is active or not</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Moveable:GetRoom">Moveable:GetRoom()</a></td>
<td class="summary">Get the current room of the object</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetRoom">Moveable:SetRoom(ID)</a></td>
<td class="summary">Set room of object
This is used in conjunction with SetPosition to teleport an item to a new room.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetPosition">Moveable:GetPosition()</a></td>
<td class="summary">Get the object's position</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetJointPosition">Moveable:GetJointPosition()</a></td> <td class="name" ><a href="#Moveable:GetJointPosition">Moveable:GetJointPosition()</a></td>
<td class="summary">Get the object's joint position</td> <td class="summary">Get the object's joint position</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetPosition">Moveable:SetPosition(position)</a></td>
<td class="summary">Set the moveable's position
If you are moving a moveable whose behaviour involves knowledge of room geometry,
(e.g.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Moveable:GetRotation">Moveable:GetRotation()</a></td> <td class="name" ><a href="#Moveable:GetRotation">Moveable:GetRotation()</a></td>
@ -318,6 +278,44 @@ associated getters and setters.</td>
<td class="name" ><a href="#Moveable:Destroy">Moveable:Destroy()</a></td> <td class="name" ><a href="#Moveable:Destroy">Moveable:Destroy()</a></td>
<td class="summary">Destroy the moveable.</td> <td class="summary">Destroy the moveable.</td>
</tr> </tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnHit">Moveable:SetOnHit(callback)</a></td>
<td class="summary">Set the name of the function to be called when the moveable is shot by Lara
Note that this will be triggered twice when shot with both pistols at once.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnKilled">Moveable:SetOnKilled(callback)</a></td>
<td class="summary">Set the name of the function to be called when the moveable is destroyed/killed
Note that enemy death often occurs at the end of an animation, and not at the exact moment
the enemy's HP becomes zero.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithObject">Moveable:SetOnCollidedWithObject(func)</a></td>
<td class="summary">Set the function to be called called when this moveable collides with another moveable</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetOnCollidedWithRoom">Moveable:SetOnCollidedWithRoom(func)</a></td>
<td class="summary">Set the function called when this moveable collides with room geometry (e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetPosition">Moveable:GetPosition()</a></td>
<td class="summary">Get the object's position</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetPosition">Moveable:SetPosition(position[, updateRoom])</a></td>
<td class="summary">Set the moveable's position
If you are moving a moveable whose behaviour involves knowledge of room geometry,
(e.g.</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:GetRoom">Moveable:GetRoom()</a></td>
<td class="summary">Get the current room of the object</td>
</tr>
<tr>
<td class="name" ><a href="#Moveable:SetRoom">Moveable:SetRoom(ID)</a></td>
<td class="summary">Set room of object
Use this if you are not using SetPosition's automatic room update - for example, when dealing with overlapping rooms.</td>
</tr>
</table> </table>
<br/> <br/>
@ -329,7 +327,7 @@ associated getters and setters.</td>
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "Moveable"></a> <a name = "Moveable"></a>
<strong>Moveable(object, name, position, rotation, room, animNumber, frameNumber, hp, OCB, AIBits)</strong> <strong>Moveable(object, name, position[, rotation[, room[, animNumber=0[, frameNumber=0[, hp=10[, OCB=0[, AIBits]]]]]]])</strong>
</dt> </dt>
<dd> <dd>
For more information on each parameter, see the For more information on each parameter, see the
@ -354,30 +352,37 @@ most can just be ignored (see usage).
<li><span class="parameter">rotation</span> <li><span class="parameter">rotation</span>
<span class="types"><span class="type">Rotation</span></span> <span class="types"><span class="type">Rotation</span></span>
rotation about x, y, and z axes (default Rotation(0, 0, 0)) rotation about x, y, and z axes (default Rotation(0, 0, 0))
(<em>optional</em>)
</li> </li>
<li><span class="parameter">room</span> <li><span class="parameter">room</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
room ID item is in room ID item is in (default: calculated automatically)
(<em>optional</em>)
</li> </li>
<li><span class="parameter">animNumber</span> <li><span class="parameter">animNumber</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
anim number (default 0) anim number
(<em>default</em> 0)
</li> </li>
<li><span class="parameter">frameNumber</span> <li><span class="parameter">frameNumber</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
frame number (default 0) frame number
(<em>default</em> 0)
</li> </li>
<li><span class="parameter">hp</span> <li><span class="parameter">hp</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
HP of item (default 10) HP of item
(<em>default</em> 10)
</li> </li>
<li><span class="parameter">OCB</span> <li><span class="parameter">OCB</span>
<span class="types"><span class="type">int</span></span> <span class="types"><span class="type">int</span></span>
ocb of item (default 0) ocb of item (default 0)
(<em>default</em> 0)
</li> </li>
<li><span class="parameter">AIBits</span> <li><span class="parameter">AIBits</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span> <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a></span>
table with AI bits (default {0,0,0,0,0,0}) table with AI bits (default {0,0,0,0,0,0})
(<em>optional</em>)
</li> </li>
</ul> </ul>
@ -394,10 +399,8 @@ most can just be ignored (see usage).
<pre class="example"><span class="keyword">local</span> item = Moveable( <pre class="example"><span class="keyword">local</span> item = Moveable(
TEN.ObjID.PISTOLS_ITEM, <span class="comment">-- object id TEN.ObjID.PISTOLS_ITEM, <span class="comment">-- object id
</span> <span class="string">"test"</span>, <span class="comment">-- name </span> <span class="string">"test"</span>, <span class="comment">-- name
</span> Vec3(<span class="number">18907</span>, <span class="number">0</span>, <span class="number">21201</span>), </span> Vec3(<span class="number">18907</span>, <span class="number">0</span>, <span class="number">21201</span>)
Rotation(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>), )</pre>
<span class="number">0</span>, <span class="comment">-- room
</span> )</pre>
</ul> </ul>
</dd> </dd>
@ -495,176 +498,6 @@ most can just be ignored (see usage).
</dd>
<dt>
<a name = "Moveable:SetOnHit"></a>
<strong>Moveable:SetOnHit(name)</strong>
</dt>
<dd>
Set the name of the function to be called when the moveable is shot by Lara
Note that this will be triggered twice when shot with both pistols at once.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">name</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
of callback function to be called
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:GetOnHit"></a>
<strong>Moveable:GetOnHit()</strong>
</dt>
<dd>
Get the name of the function called when this moveable is shot
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
name of the function
</ol>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithObject"></a>
<strong>Moveable:SetOnCollidedWithObject(name)</strong>
</dt>
<dd>
Set the name of the function called when this moveable collides with another moveable
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">name</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
of callback function to be called
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:GetOnCollidedWithObject"></a>
<strong>Moveable:GetOnCollidedWithObject()</strong>
</dt>
<dd>
Get the name of the function called when this moveable collides with another moveable
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
name of the function
</ol>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithRoom"></a>
<strong>Moveable:SetOnCollidedWithRoom(name)</strong>
</dt>
<dd>
Set the name of the function called when this moveable collides with room geometry (e.g. a wall or floor)
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">name</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
of callback function to be called
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:GetOnCollidedWithRoom"></a>
<strong>Moveable:GetOnCollidedWithRoom()</strong>
</dt>
<dd>
Get the name of the function called when this moveable collides with room geometry (e.g. a wall or floor)
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
name of the function
</ol>
</dd>
<dt>
<a name = "Moveable:SetOnKilled"></a>
<strong>Moveable:SetOnKilled(callback)</strong>
</dt>
<dd>
Set the name of the function to be called when the moveable is destroyed/killed
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">callback</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
name of function to be called
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.baddyKilled = <span class="keyword">function</span>(theBaddy) <span class="global">print</span>(<span class="string">"You killed a baddy!"</span>) <span class="keyword">end</span>
baddy:SetOnKilled(<span class="string">"baddyKilled"</span>)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:GetOnKilled"></a>
<strong>Moveable:GetOnKilled()</strong>
</dt>
<dd>
Get the name of the function called when this moveable is killed
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
name of the function
</ol>
</dd> </dd>
<dt> <dt>
<a name = "Moveable:GetObjectID"></a> <a name = "Moveable:GetObjectID"></a>
@ -886,6 +719,27 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)</pre>
</dd>
<dt>
<a name = "Moveable:GetSlotHP"></a>
<strong>Moveable:GetSlotHP(ID)</strong>
</dt>
<dd>
Get HP definded for that object type (hit points/health points) (Read Only).
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">ID</span>
<span class="types"><span class="type">int</span></span>
of the moveable slot type.
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "Moveable:GetOCB"></a> <a name = "Moveable:GetOCB"></a>
@ -927,6 +781,47 @@ shiva:SetObjectID(TEN.Objects.ObjID.BIGMEDI_ITEM)</pre>
</dd>
<dt>
<a name = "Moveable:GetItemFlags"></a>
<strong>Moveable:GetItemFlags()</strong>
</dt>
<dd>
Get the value stored in ItemFlags[x] (x is the value of the parameter)
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">short</span></span>
id of the ItemFlags array
</ol>
</dd>
<dt>
<a name = "Moveable:SetItemFlags"></a>
<strong>Moveable:SetItemFlags(value)</strong>
</dt>
<dd>
Stores the value of the first parameter in the ItemFlags[x] (x is the value of the second parameter)
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">value</span>
<span class="types"><span class="type">short</span></span>
to store in the moveable's ItemFlags[x], short id of ItemFlags array to store the value.
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "Moveable:GetColor"></a> <a name = "Moveable:GetColor"></a>
@ -1242,74 +1137,6 @@ sas:SetAIBits({<span class="number">1</span>, <span class="number">0</span>, <sp
</dd>
<dt>
<a name = "Moveable:GetRoom"></a>
<strong>Moveable:GetRoom()</strong>
</dt>
<dd>
Get the current room of the object
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
number representing the current room of the object
</ol>
</dd>
<dt>
<a name = "Moveable:SetRoom"></a>
<strong>Moveable:SetRoom(ID)</strong>
</dt>
<dd>
Set room of object
This is used in conjunction with SetPosition to teleport an item to a new room.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">ID</span>
<span class="types"><span class="type">int</span></span>
the ID of the new room
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> sas = TEN.Objects.GetMoveableByName(<span class="string">"sas_enemy"</span>)
sas:SetRoom(destinationRoom)
sas:SetPosition(destinationPosition)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:GetPosition"></a>
<strong>Moveable:GetPosition()</strong>
</dt>
<dd>
Get the object's position
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">Vec3</span></span>
a copy of the moveable's position
</ol>
</dd> </dd>
<dt> <dt>
<a name = "Moveable:GetJointPosition"></a> <a name = "Moveable:GetJointPosition"></a>
@ -1330,30 +1157,6 @@ sas:SetPosition(destinationPosition)</pre>
</dd>
<dt>
<a name = "Moveable:SetPosition"></a>
<strong>Moveable:SetPosition(position)</strong>
</dt>
<dd>
Set the moveable's position
If you are moving a moveable whose behaviour involves knowledge of room geometry,
(e.g. a BADDY1, which uses it for pathfinding), then you <em>must</em> use this in conjunction
with <a href="../2 classes/Objects.Moveable.html#Moveable:SetRoom">Moveable:SetRoom</a>. Otherwise, said moveable will not behave correctly.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">position</span>
<span class="types"><span class="type">Vec3</span></span>
the new position of the moveable
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "Moveable:GetRotation"></a> <a name = "Moveable:GetRotation"></a>
@ -1480,6 +1283,195 @@ sas:SetPosition(destinationPosition)</pre>
</dd>
<dt>
<a name = "Moveable:SetOnHit"></a>
<strong>Moveable:SetOnHit(callback)</strong>
</dt>
<dd>
Set the name of the function to be called when the moveable is shot by Lara
Note that this will be triggered twice when shot with both pistols at once.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">callback</span>
<span class="types"><span class="type">function</span></span>
function in LevelFuncs hierarchy to call when moveable is shot
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnKilled"></a>
<strong>Moveable:SetOnKilled(callback)</strong>
</dt>
<dd>
Set the name of the function to be called when the moveable is destroyed/killed
Note that enemy death often occurs at the end of an animation, and not at the exact moment
the enemy's HP becomes zero.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">callback</span>
<span class="types"><span class="type">function</span></span>
function in LevelFuncs hierarchy to call when enemy is killed
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">LevelFuncs.baddyKilled = <span class="keyword">function</span>(theBaddy) <span class="global">print</span>(<span class="string">"You killed a baddy!"</span>) <span class="keyword">end</span>
baddy:SetOnKilled(LevelFuncs.baddyKilled)</pre>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithObject"></a>
<strong>Moveable:SetOnCollidedWithObject(func)</strong>
</dt>
<dd>
Set the function to be called called when this moveable collides with another moveable
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy)
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:SetOnCollidedWithRoom"></a>
<strong>Moveable:SetOnCollidedWithRoom(func)</strong>
</dt>
<dd>
Set the function called when this moveable collides with room geometry (e.g. a wall or floor)
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">func</span>
<span class="types"><span class="type">function</span></span>
callback function to be called (must be in LevelFuncs hierarchy)
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:GetPosition"></a>
<strong>Moveable:GetPosition()</strong>
</dt>
<dd>
Get the object's position
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">Vec3</span></span>
a copy of the moveable's position
</ol>
</dd>
<dt>
<a name = "Moveable:SetPosition"></a>
<strong>Moveable:SetPosition(position[, updateRoom])</strong>
</dt>
<dd>
Set the moveable's position
If you are moving a moveable whose behaviour involves knowledge of room geometry,
(e.g. a BADDY1, which uses it for pathfinding), then the second argument should
be true (or omitted, as true is the default). Otherwise, said moveable will not behave correctly.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">position</span>
<span class="types"><span class="type">Vec3</span></span>
the new position of the moveable
</li>
<li><span class="parameter">updateRoom</span>
<span class="types"><span class="type">bool</span></span>
Will room changes be automatically detected? Set to false if you are using overlapping rooms (default: true)
(<em>optional</em>)
</li>
</ul>
</dd>
<dt>
<a name = "Moveable:GetRoom"></a>
<strong>Moveable:GetRoom()</strong>
</dt>
<dd>
Get the current room of the object
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">int</span></span>
number representing the current room of the object
</ol>
</dd>
<dt>
<a name = "Moveable:SetRoom"></a>
<strong>Moveable:SetRoom(ID)</strong>
</dt>
<dd>
Set room of object
Use this if you are not using SetPosition's automatic room update - for example, when dealing with overlapping rooms.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">ID</span>
<span class="types"><span class="type">int</span></span>
the ID of the new room
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> sas = TEN.Objects.GetMoveableByName(<span class="string">"sas_enemy"</span>)
sas:SetRoom(destinationRoom)
sas:SetPosition(destinationPosition, <span class="keyword">false</span>)</pre>
</ul>
</dd> </dd>
</dl> </dl>
@ -1488,7 +1480,7 @@ sas:SetPosition(destinationPosition)</pre>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -262,7 +262,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -260,7 +260,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -112,6 +112,14 @@
<td class="summary">Set the static's rotation</td> <td class="summary">Set the static's rotation</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#Static:GetScale">Static:GetScale()</a></td>
<td class="summary">Get the static's scale</td>
</tr>
<tr>
<td class="name" ><a href="#Static:SetScale">Static:SetScale(scale)</a></td>
<td class="summary">Set the static's scale</td>
</tr>
<tr>
<td class="name" ><a href="#Static:GetName">Static:GetName()</a></td> <td class="name" ><a href="#Static:GetName">Static:GetName()</a></td>
<td class="summary">Get the static's unique string identifier</td> <td class="summary">Get the static's unique string identifier</td>
</tr> </tr>
@ -226,6 +234,47 @@
</dd>
<dt>
<a name = "Static:GetScale"></a>
<strong>Static:GetScale()</strong>
</dt>
<dd>
Get the static's scale
<h3>Returns:</h3>
<ol>
<span class="types"><span class="type">float</span></span>
current static scale
</ol>
</dd>
<dt>
<a name = "Static:SetScale"></a>
<strong>Static:SetScale(scale)</strong>
</dt>
<dd>
Set the static's scale
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">scale</span>
<span class="types"><span class="type">Scale</span></span>
the static's new scale
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "Static:GetName"></a> <a name = "Static:GetName"></a>
@ -358,7 +407,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -307,7 +307,7 @@ TEN.Strings.DisplayStringOption.SHADOW -- will give the text a small shadow
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -312,7 +312,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -250,7 +250,7 @@ All values will be clamped to [-32768, 32767].</p>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -277,7 +277,7 @@ However, this function would return it as (0, 1, 1).
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -149,7 +149,7 @@ ALPHABLEND
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -358,7 +358,7 @@ EXAMINE_ITEM8_COMBO2
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -1096,7 +1096,7 @@ PANEL_MIDDLE_CORNER
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -90,10 +90,13 @@
<p>Event sequence - a chain of functions to call at specified times, modeled after TRNG's organizers.</p> <p>Event sequence - a chain of functions to call at specified times, modeled after TRNG's organizers.</p>
<p> <p>
<p> Works atop the Timer, and so is updated automatically pre-OnControlPhase, and saved automatically when the game is saved.</p>
<p> Example usage:</p> <p> Example usage:</p>
<pre> <pre>
<span class="keyword">local</span> EventSequence = <span class="global">require</span>(<span class="string">"EventSequence"</span>) <span class="keyword">local</span> EventSequence = <span class="global">require</span>(<span class="string">"Engine.EventSequence"</span>)
<span class="comment">-- These will be called by the sequence <span class="comment">-- These will be called by the sequence
</span>LevelFuncs.HealLara = <span class="keyword">function</span>() </span>LevelFuncs.HealLara = <span class="keyword">function</span>()
@ -115,22 +118,17 @@ LevelFuncs.SpawnBaddy = <span class="keyword">function</span>(baddy, name, pos)
<span class="keyword">false</span>, <span class="comment">-- does not loop <span class="keyword">false</span>, <span class="comment">-- does not loop
</span> {seconds = <span class="keyword">true</span>, deciseconds = <span class="keyword">true</span>}, <span class="comment">-- timer format, see Timer for details </span> {seconds = <span class="keyword">true</span>, deciseconds = <span class="keyword">true</span>}, <span class="comment">-- timer format, see Timer for details
</span> <span class="number">6</span>, <span class="comment">-- seconds until call the function specified in next arg </span> <span class="number">6</span>, <span class="comment">-- seconds until call the function specified in next arg
</span> <span class="string">"HealLara"</span>, <span class="comment">-- first function to call. If we don't need to pass any arguments, we can just give the func name as a string </span> LevelFuncs.HealLara, <span class="comment">-- first function to call. If we don't need to pass any arguments, we can just pass the function
</span> <span class="number">2.1</span>, <span class="comment">-- seconds until the next function, after the previous one has been called </span> <span class="number">2.1</span>, <span class="comment">-- seconds until the next function, after the previous one has been called
</span> {<span class="string">"SpawnBaddy"</span>, TEN.Objects.ObjID.BADDY1, <span class="string">"steve"</span>, posSteve}, <span class="comment">-- if we DO want to pass arguments to the function to be called, we give a table with the name of the function ("SpawnBaddy" in this case) followed by the args to pass to it </span> {LevelFuncs.SpawnBaddy, TEN.Objects.ObjID.BADDY1, <span class="string">"steve"</span>, posSteve}, <span class="comment">-- if we DO want to pass arguments to the function to be called, we give a table with the function (LevelFuncs.SpawnBaddy in this case) followed by the args to pass to it
</span> <span class="number">0.5</span>, </span> <span class="number">0.5</span>,
{<span class="string">"SpawnBaddy"</span>, TEN.Objects.ObjID.SAS_CAIRO, <span class="string">"chris"</span>, posChris}, {LevelFuncs.SpawnBaddy, TEN.Objects.ObjID.SAS_CAIRO, <span class="string">"chris"</span>, posChris},
<span class="number">1</span>, <span class="number">1</span>,
<span class="string">"HealLara"</span>) LevelFuncs.HealLara)
<span class="comment">-- event sequences are inactive to begin with and so need to be started <span class="comment">-- event sequences are inactive to begin with and so need to be started
</span> mySeq:Start() </span> mySeq:Start()
<span class="keyword">end</span> <span class="keyword">end</span>
<span class="comment">-- EventSequence runs on Timer, so this call is required
</span>LevelFuncs.OnControlPhase = <span class="keyword">function</span>(dt)
Timer.UpdateAll(dt)
<span class="keyword">end</span>
</pre> </pre>
</p> </p>
@ -333,7 +331,7 @@ LevelFuncs.SpawnBaddy = <span class="keyword">function</span>(baddy, name, pos)
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -90,10 +90,13 @@
<p>Basic timer - after a specified number of seconds, the specified thing happens.</p> <p>Basic timer - after a specified number of seconds, the specified thing happens.</p>
<p> <p>
<p> Timers are updated automatically every frame before OnControlPhase.</p>
<p> Example usage:</p> <p> Example usage:</p>
<pre> <pre>
<span class="keyword">local</span> Timer = <span class="global">require</span>(<span class="string">"Timer"</span>) <span class="keyword">local</span> Timer = <span class="global">require</span>(<span class="string">"Engine.Timer"</span>)
<span class="comment">-- This will be called when the timer runs out <span class="comment">-- This will be called when the timer runs out
</span>LevelFuncs.FinishTimer = <span class="keyword">function</span>(healthWhenStarted, victoryMessage) </span>LevelFuncs.FinishTimer = <span class="keyword">function</span>(healthWhenStarted, victoryMessage)
@ -107,15 +110,11 @@
<span class="number">5.0</span>, <span class="number">5.0</span>,
<span class="keyword">false</span>, <span class="keyword">false</span>,
{minutes = <span class="keyword">false</span>, seconds = <span class="keyword">true</span>, deciseconds = <span class="keyword">true</span>}, {minutes = <span class="keyword">false</span>, seconds = <span class="keyword">true</span>, deciseconds = <span class="keyword">true</span>},
<span class="string">"FinishTimer"</span>, LevelFuncs.FinishTimer,
Lara:GetHP(), Lara:GetHP(),
<span class="string">"Well done!"</span>) <span class="string">"Well done!"</span>)
myTimer:Start() myTimer:Start()
<span class="keyword">end</span> <span class="keyword">end</span>
LevelFuncs.OnControlPhase = <span class="keyword">function</span>(dt)
Timer.UpdateAll(dt)
<span class="keyword">end</span>
</pre> </pre>
</p> </p>
@ -124,7 +123,7 @@ LevelFuncs.OnControlPhase = <span class="keyword">function</span>(dt)
<h2><a href="#Functions">Functions</a></h2> <h2><a href="#Functions">Functions</a></h2>
<table class="function_list"> <table class="function_list">
<tr> <tr>
<td class="name" ><a href="#Create">Create(name, totalTime, loop, timerFormat, func[, ...])</a></td> <td class="name" ><a href="#func">func(name, totalTime, loop, timerFormat[, ...])</a></td>
<td class="summary">Create (but do not start) a new timer.</td> <td class="summary">Create (but do not start) a new timer.</td>
</tr> </tr>
<tr> <tr>
@ -132,10 +131,6 @@ LevelFuncs.OnControlPhase = <span class="keyword">function</span>(dt)
<td class="summary">Get a timer by its name.</td> <td class="summary">Get a timer by its name.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#UpdateAll">UpdateAll(dt)</a></td>
<td class="summary">Update all active timers.</td>
</tr>
<tr>
<td class="name" ><a href="#myTimer:SetFunction">myTimer:SetFunction(func[, ...])</a></td> <td class="name" ><a href="#myTimer:SetFunction">myTimer:SetFunction(func[, ...])</a></td>
<td class="summary">Give the timer a new function and args</td> <td class="summary">Give the timer a new function and args</td>
</tr> </tr>
@ -189,8 +184,8 @@ LevelFuncs.OnControlPhase = <span class="keyword">function</span>(dt)
<dl class="function"> <dl class="function">
<dt> <dt>
<a name = "Create"></a> <a name = "func"></a>
<strong>Create(name, totalTime, loop, timerFormat, func[, ...])</strong> <strong>func(name, totalTime, loop, timerFormat[, ...])</strong>
</dt> </dt>
<dd> <dd>
Create (but do not start) a new timer. </p> Create (but do not start) a new timer. </p>
@ -214,7 +209,10 @@ local myTimeFormat4 = {seconds = true}
<p><strong>At any given time, only one timer can show its countdown</strong>.</p> <p><strong>At any given time, only one timer can show its countdown</strong>.</p>
<p><strong>Do not give your timers a name beginning with __TEN, as this is reserved for timers used by other internal libaries</strong>.</p>
<p>Use this sparingly; in the classics, timed challenges did not have visible countdowns. For shorter timers, the gameplay benefit from showing the remaining time might not be necessary, and could interfere with the atmosphere of the level. <p>Use this sparingly; in the classics, timed challenges did not have visible countdowns. For shorter timers, the gameplay benefit from showing the remaining time might not be necessary, and could interfere with the atmosphere of the level.
The LevelFunc function to call when the time is up
<h3>Parameters:</h3> <h3>Parameters:</h3>
@ -235,10 +233,6 @@ local myTimeFormat4 = {seconds = true}
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">bool</span></span> <span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.5">table</a> or <span class="type">bool</span></span>
If a table is given, the remaining time will be shown as a string, formatted according to the values in the table. If true, the remaining seconds, rounded up, will show at the bottom of the screen. If false, the remaining time will not be shown on screen. If a table is given, the remaining time will be shown as a string, formatted according to the values in the table. If true, the remaining seconds, rounded up, will show at the bottom of the screen. If false, the remaining time will not be shown on screen.
</li> </li>
<li><span class="parameter">func</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span>
The name of the LevelFunc member to call when the time is upssss
</li>
<li><span class="parameter">...</span> <li><span class="parameter">...</span>
a variable number of arguments with which the above function will be called a variable number of arguments with which the above function will be called
(<em>optional</em>) (<em>optional</em>)
@ -280,28 +274,6 @@ local myTimeFormat4 = {seconds = true}
</dd>
<dt>
<a name = "UpdateAll"></a>
<strong>UpdateAll(dt)</strong>
</dt>
<dd>
Update all active timers.
Should be called in LevelFuncs.OnControlPhase
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">dt</span>
<span class="types"><span class="type">number</span></span>
The time in seconds since the last frame
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "myTimer:SetFunction"></a> <a name = "myTimer:SetFunction"></a>
@ -314,8 +286,8 @@ local myTimeFormat4 = {seconds = true}
<h3>Parameters:</h3> <h3>Parameters:</h3>
<ul> <ul>
<li><span class="parameter">func</span> <li><span class="parameter">func</span>
<span class="types"><a class="type" href="https://www.lua.org/manual/5.1/manual.html#5.4">string</a></span> <span class="types"><span class="type">function</span></span>
The name of the LevelFunc member to call when the time is up The LevelFunc member to call when the time is up
</li> </li>
<li><span class="parameter">...</span> <li><span class="parameter">...</span>
a variable number of arguments with which the above function will be called a variable number of arguments with which the above function will be called
@ -524,7 +496,7 @@ local myTimeFormat4 = {seconds = true}
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" /> <link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -138,7 +138,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-11 22:43:52 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -3,7 +3,7 @@
<html> <html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head> <head>
<title>TombEngine 1.0.1 Lua API</title> <title>TombEngine 1.0.2 Lua API</title>
<link rel="stylesheet" href="ldoc.css" type="text/css" /> <link rel="stylesheet" href="ldoc.css" type="text/css" />
</head> </head>
<body> <body>
@ -80,7 +80,7 @@
<div id="content"> <div id="content">
<h2>TombEngine 1.0.1 scripting interface</h2> <h2>TombEngine 1.0.2 scripting interface</h2>
<p>Welcome to the TombEngine scripting API. This is a work in progress and some information might be wrong or outdated. Please also note that this is primarily a reference document, not a tutorial, so expect descriptions to be fairly sparse.</p> <p>Welcome to the TombEngine scripting API. This is a work in progress and some information might be wrong or outdated. Please also note that this is primarily a reference document, not a tutorial, so expect descriptions to be fairly sparse.</p>
<p>At the time of writing, there is a tutorial describing the basics of Lua, as well as a number of example scripts, on the wiki <a href="https://github.com/Stranger1992/Tomb-Engine-Demo-Levels/wiki">here</a>.</p> <p>At the time of writing, there is a tutorial describing the basics of Lua, as well as a number of example scripts, on the wiki <a href="https://github.com/Stranger1992/Tomb-Engine-Demo-Levels/wiki">here</a>.</p>
@ -90,7 +90,7 @@ For example, to call GetMoveableByName, you would have to do:</p>
<pre><code>local door = TEN.Objects.GetMoveableByName("door_type4_14") <pre><code>local door = TEN.Objects.GetMoveableByName("door_type4_14")
</code></pre> </code></pre>
<p>To save on typing, you can put the following at the start of a Lua file:</p> <p>To save on typing, you can put the following at the start of a Lua file:</p>
<pre><code>local Util = require("Util") <pre><code>local Util = require("Engine.Util")
Util.ShortenTENCalls() Util.ShortenTENCalls()
</code></pre> </code></pre>
<p>This will put the modules and classes in the global table. In other words, it means you can do the following:</p> <p>This will put the modules and classes in the global table. In other words, it means you can do the following:</p>
@ -248,7 +248,7 @@ Util.ShortenTENCalls()
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-08-14 20:33:42 </i> <i style="float:right;">Last updated 2022-09-07 20:56:06 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -1,7 +1,10 @@
----- -----
--- Event sequence - a chain of functions to call at specified times, modeled after TRNG's organizers. --- Event sequence - a chain of functions to call at specified times, modeled after TRNG's organizers.
--
-- Works atop the Timer, and so is updated automatically pre-OnControlPhase, and saved automatically when the game is saved.
--
-- Example usage: -- Example usage:
-- local EventSequence = require("EventSequence") -- local EventSequence = require("Engine.EventSequence")
-- --
-- -- These will be called by the sequence -- -- These will be called by the sequence
-- LevelFuncs.HealLara = function() -- LevelFuncs.HealLara = function()
@ -23,34 +26,30 @@
-- false, -- does not loop -- false, -- does not loop
-- {seconds = true, deciseconds = true}, -- timer format, see Timer for details -- {seconds = true, deciseconds = true}, -- timer format, see Timer for details
-- 6, -- seconds until call the function specified in next arg -- 6, -- seconds until call the function specified in next arg
-- "HealLara", -- first function to call. If we don't need to pass any arguments, we can just give the func name as a string -- LevelFuncs.HealLara, -- first function to call. If we don't need to pass any arguments, we can just pass the function
-- 2.1, -- seconds until the next function, after the previous one has been called -- 2.1, -- seconds until the next function, after the previous one has been called
-- {"SpawnBaddy", TEN.Objects.ObjID.BADDY1, "steve", posSteve}, -- if we DO want to pass arguments to the function to be called, we give a table with the name of the function ("SpawnBaddy" in this case) followed by the args to pass to it -- {LevelFuncs.SpawnBaddy, TEN.Objects.ObjID.BADDY1, "steve", posSteve}, -- if we DO want to pass arguments to the function to be called, we give a table with the function (LevelFuncs.SpawnBaddy in this case) followed by the args to pass to it
-- 0.5, -- 0.5,
-- {"SpawnBaddy", TEN.Objects.ObjID.SAS_CAIRO, "chris", posChris}, -- {LevelFuncs.SpawnBaddy, TEN.Objects.ObjID.SAS_CAIRO, "chris", posChris},
-- 1, -- 1,
-- "HealLara") -- LevelFuncs.HealLara)
-- --
-- -- event sequences are inactive to begin with and so need to be started -- -- event sequences are inactive to begin with and so need to be started
-- mySeq:Start() -- mySeq:Start()
-- end -- end
-- --
-- -- EventSequence runs on Timer, so this call is required
-- LevelFuncs.OnControlPhase = function(dt)
-- Timer.UpdateAll(dt)
-- end
--
-- @luautil EventSequence -- @luautil EventSequence
local Timer = require("Timer") local Timer = require("Timer")
local EventSequence local EventSequence
LevelVars.__TEN_eventSequence = {sequences = {}} LevelFuncs.Engine.EventSequence = {}
LevelVars.Engine.EventSequence = {sequences = {}}
LevelFuncs.__TEN_eventSequence_callNext = function(sequenceName, nextTimerName, func, ...) LevelFuncs.Engine.EventSequence.CallNext = function(sequenceName, nextTimerName, func, ...)
local thisES = LevelVars.__TEN_eventSequence.sequences[sequenceName] local thisES = LevelVars.Engine.EventSequence.sequences[sequenceName]
LevelFuncs[func](...) func(...)
thisES.currentTimer = thisES.currentTimer + 1 thisES.currentTimer = thisES.currentTimer + 1
if thisES.currentTimer <= #thisES.timers then if thisES.currentTimer <= #thisES.timers then
@ -82,9 +81,8 @@ EventSequence = {
setmetatable(obj, mt) setmetatable(obj, mt)
obj.name = name obj.name = name
LevelVars.Engine.EventSequence.sequences[name] = {}
LevelVars.__TEN_eventSequence.sequences[name] = {} local thisES = LevelVars.Engine.EventSequence.sequences[name]
local thisES = LevelVars.__TEN_eventSequence.sequences[name]
thisES.name = name thisES.name = name
thisES.timesFuncsAndArgs = {...} thisES.timesFuncsAndArgs = {...}
thisES.loop = loop thisES.loop = loop
@ -99,7 +97,6 @@ EventSequence = {
local nextTimer = i + 2 local nextTimer = i + 2
local timerIndex = #thisES.timers + 1 local timerIndex = #thisES.timers + 1
local funcName = "__TEN_eventSequence_" .. name .. "_func" .. timerIndex
local timerName = "__TEN_eventSequence_" .. name .. "_timer" .. timerIndex local timerName = "__TEN_eventSequence_" .. name .. "_timer" .. timerIndex
local nextTimerName = "__TEN_eventSequence_" .. name .. "_timer" .. timerIndex + 1 local nextTimerName = "__TEN_eventSequence_" .. name .. "_timer" .. timerIndex + 1
@ -110,7 +107,7 @@ EventSequence = {
thisES.firstTimerName = timerName thisES.firstTimerName = timerName
end end
if type(funcAndArgs) == "string" then if type(funcAndArgs) == "userdata" then
-- we only have a function -- we only have a function
func = funcAndArgs func = funcAndArgs
funcAndArgs = {} funcAndArgs = {}
@ -123,7 +120,7 @@ EventSequence = {
tfa[i], -- time tfa[i], -- time
false, false,
timerFormat, timerFormat,
"__TEN_eventSequence_callNext", LevelFuncs.Engine.EventSequence.CallNext,
name, name,
nextTimerName, nextTimerName,
func, func,
@ -140,7 +137,7 @@ EventSequence = {
-- @string name The label that was given to the sequence when it was created -- @string name The label that was given to the sequence when it was created
-- @return The sequence -- @return The sequence
Get = function(name) Get = function(name)
if LevelVars.__TEN_eventSequence.sequences[name] then if LevelVars.Engine.EventSequence.sequences[name] then
local obj = {} local obj = {}
local mt = {} local mt = {}
mt.__index = EventSequence mt.__index = EventSequence
@ -155,7 +152,7 @@ EventSequence = {
-- @function mySequence:SetPaused -- @function mySequence:SetPaused
-- @bool p if true, the sequence will be paused; if false, it will be unpaused -- @bool p if true, the sequence will be paused; if false, it will be unpaused
SetPaused = function(t, p) SetPaused = function(t, p)
local thisES = LevelVars.__TEN_eventSequence.sequences[t.name] local thisES = LevelVars.Engine.EventSequence.sequences[t.name]
Timer.Get(thisES.timers[thisES.currentTimer]):SetPaused(p) Timer.Get(thisES.timers[thisES.currentTimer]):SetPaused(p)
end; end;
@ -163,21 +160,21 @@ EventSequence = {
-- @function mySequence:IsPaused -- @function mySequence:IsPaused
-- @return true if the timer is paused, false if otherwise -- @return true if the timer is paused, false if otherwise
IsPaused = function(t) IsPaused = function(t)
local thisES = LevelVars.__TEN_eventSequence.sequences[t.name] local thisES = LevelVars.Engine.EventSequence.sequences[t.name]
return Timer.Get(thisES.timers[thisES.currentTimer]):IsPaused() return Timer.Get(thisES.timers[thisES.currentTimer]):IsPaused()
end; end;
--- Begin or unpause a sequence. If showing the remaining time on-screen, its color will be set to white. --- Begin or unpause a sequence. If showing the remaining time on-screen, its color will be set to white.
-- @function mySequence:Start -- @function mySequence:Start
Start = function(t) Start = function(t)
local thisES = LevelVars.__TEN_eventSequence.sequences[t.name] local thisES = LevelVars.Engine.EventSequence.sequences[t.name]
Timer.Get(thisES.timers[thisES.currentTimer]):Start() Timer.Get(thisES.timers[thisES.currentTimer]):Start()
end; end;
--- Stop the sequence. --- Stop the sequence.
--@function mySequence:Stop --@function mySequence:Stop
Stop = function(t) Stop = function(t)
local thisES = LevelVars.__TEN_eventSequence.sequences[t.name] local thisES = LevelVars.Engine.EventSequence.sequences[t.name]
Timer.Get(thisES.timers[thisES.currentTimer]):Stop() Timer.Get(thisES.timers[thisES.currentTimer]):Stop()
end; end;
@ -185,7 +182,7 @@ EventSequence = {
-- @function mySequence:IsActive -- @function mySequence:IsActive
-- @return true if the sequence is active, false if otherwise -- @return true if the sequence is active, false if otherwise
IsActive = function(t) IsActive = function(t)
local thisES = LevelVars.__TEN_eventSequence.sequences[t.name] local thisES = LevelVars.Engine.EventSequence.sequences[t.name]
return Timer.Get(thisES.timers[thisES.currentTimer]):IsActive() return Timer.Get(thisES.timers[thisES.currentTimer]):IsActive()
end; end;
} }

View file

@ -1,7 +1,10 @@
----- -----
--- Basic timer - after a specified number of seconds, the specified thing happens. --- Basic timer - after a specified number of seconds, the specified thing happens.
--
-- Timers are updated automatically every frame before OnControlPhase.
--
-- Example usage: -- Example usage:
-- local Timer = require("Timer") -- local Timer = require("Engine.Timer")
-- --
-- -- This will be called when the timer runs out -- -- This will be called when the timer runs out
-- LevelFuncs.FinishTimer = function(healthWhenStarted, victoryMessage) -- LevelFuncs.FinishTimer = function(healthWhenStarted, victoryMessage)
@ -15,19 +18,16 @@
-- 5.0, -- 5.0,
-- false, -- false,
-- {minutes = false, seconds = true, deciseconds = true}, -- {minutes = false, seconds = true, deciseconds = true},
-- "FinishTimer", -- LevelFuncs.FinishTimer,
-- Lara:GetHP(), -- Lara:GetHP(),
-- "Well done!") -- "Well done!")
-- myTimer:Start() -- myTimer:Start()
-- end -- end
-- --
-- LevelFuncs.OnControlPhase = function(dt)
-- Timer.UpdateAll(dt)
-- end
--
-- @luautil Timer -- @luautil Timer
LevelVars.__TEN_timer = {timers = {}} LevelFuncs.Engine.Timer = {}
LevelVars.Engine.Timer = {timers = {}}
local Timer local Timer
@ -35,6 +35,7 @@ local unpausedColor = TEN.Color(255, 255, 255)
local pausedColor = TEN.Color(255, 255, 0) local pausedColor = TEN.Color(255, 255, 0)
local str = TEN.Strings.DisplayString("TIMER", 0, 0, unpausedColor, false, {TEN.Strings.DisplayStringOption.CENTER, TEN.Strings.DisplayStringOption.SHADOW} ) local str = TEN.Strings.DisplayString("TIMER", 0, 0, unpausedColor, false, {TEN.Strings.DisplayStringOption.CENTER, TEN.Strings.DisplayStringOption.SHADOW} )
Timer = { Timer = {
--- Create (but do not start) a new timer. --- Create (but do not start) a new timer.
-- --
@ -56,13 +57,15 @@ Timer = {
-- --
--__At any given time, only one timer can show its countdown__. --__At any given time, only one timer can show its countdown__.
-- --
--__Do not give your timers a name beginning with __TEN, as this is reserved for timers used by other internal libaries__.
--
--Use this sparingly; in the classics, timed challenges did not have visible countdowns. For shorter timers, the gameplay benefit from showing the remaining time might not be necessary, and could interfere with the atmosphere of the level. --Use this sparingly; in the classics, timed challenges did not have visible countdowns. For shorter timers, the gameplay benefit from showing the remaining time might not be necessary, and could interfere with the atmosphere of the level.
-- --
-- @string name A label to give this timer; used to retrieve the timer later -- @string name A label to give this timer; used to retrieve the timer later
-- @number totalTime The duration of the timer, in seconds -- @number totalTime The duration of the timer, in seconds
-- @bool loop if true, the timer will start again immediately after the time has elapsed -- @bool loop if true, the timer will start again immediately after the time has elapsed
-- @tparam ?table|bool timerFormat If a table is given, the remaining time will be shown as a string, formatted according to the values in the table. If true, the remaining seconds, rounded up, will show at the bottom of the screen. If false, the remaining time will not be shown on screen. -- @tparam ?table|bool timerFormat If a table is given, the remaining time will be shown as a string, formatted according to the values in the table. If true, the remaining seconds, rounded up, will show at the bottom of the screen. If false, the remaining time will not be shown on screen.
-- @string func The name of the LevelFunc member to call when the time is upssss -- @function func The LevelFunc function to call when the time is up
-- @param[opt] ... a variable number of arguments with which the above function will be called -- @param[opt] ... a variable number of arguments with which the above function will be called
-- @return The timer in its paused state -- @return The timer in its paused state
-- --
@ -73,8 +76,13 @@ Timer = {
setmetatable(obj, mt) setmetatable(obj, mt)
obj.name = name obj.name = name
LevelVars.__TEN_timer.timers[name] ={}
local thisTimer = LevelVars.__TEN_timer.timers[name] if LevelVars.Engine.Timer.timers[name] then
print("Warning: a timer with name " .. name .. " already exists.")
end
LevelVars.Engine.Timer.timers[name] ={}
local thisTimer = LevelVars.Engine.Timer.timers[name]
thisTimer.name = name thisTimer.name = name
thisTimer.totalTime = totalTime thisTimer.totalTime = totalTime
thisTimer.remainingTime = totalTime thisTimer.remainingTime = totalTime
@ -95,7 +103,7 @@ Timer = {
-- @string name The label that was given to the timer when it was created -- @string name The label that was given to the timer when it was created
-- @return The timer -- @return The timer
Get = function(name) Get = function(name)
if LevelVars.__TEN_timer.timers[name] then if LevelVars.Engine.Timer.timers[name] then
local obj = {} local obj = {}
local mt = {} local mt = {}
mt.__index = Timer mt.__index = Timer
@ -112,8 +120,7 @@ Timer = {
t.remainingTime = t.remainingTime - dt t.remainingTime = t.remainingTime - dt
if t.remainingTime <= 0 then if t.remainingTime <= 0 then
LevelFuncs[t.func](table.unpack(t.funcArgs)) t.func(table.unpack(t.funcArgs))
if not t.loop then if not t.loop then
t.active = false t.active = false
else else
@ -190,21 +197,16 @@ Timer = {
end end
end; end;
--- Update all active timers.
-- Should be called in LevelFuncs.OnControlPhase
-- @number dt The time in seconds since the last frame
UpdateAll = function(dt) UpdateAll = function(dt)
for _, t in pairs(LevelVars.__TEN_timer.timers) do print("Timer.UpdateAll is deprecated; timers and event sequences now get updated automatically pre-control phase.")
Timer.Update(t, dt)
end
end; end;
--- Give the timer a new function and args --- Give the timer a new function and args
-- @function myTimer:SetFunction -- @function myTimer:SetFunction
-- @string func The name of the LevelFunc member to call when the time is up -- @tparam function func The LevelFunc member to call when the time is up
-- @param[opt] ... a variable number of arguments with which the above function will be called -- @param[opt] ... a variable number of arguments with which the above function will be called
SetFunction = function(t, func, ...) SetFunction = function(t, func, ...)
local thisTimer = LevelVars.__TEN_timer.timers[t.name] local thisTimer = LevelVars.Engine.Timer.timers[t.name]
thisTimer.func = func thisTimer.func = func
thisTimer.funcArgs = {...} thisTimer.funcArgs = {...}
end; end;
@ -212,7 +214,7 @@ Timer = {
--- Begin or unpause a timer. If showing the remaining time on-screen, its color will be set to white. --- Begin or unpause a timer. If showing the remaining time on-screen, its color will be set to white.
-- @function myTimer:Start -- @function myTimer:Start
Start = function(t) Start = function(t)
local thisTimer = LevelVars.__TEN_timer.timers[t.name] local thisTimer = LevelVars.Engine.Timer.timers[t.name]
if not thisTimer.active then if not thisTimer.active then
thisTimer.active = true thisTimer.active = true
end end
@ -227,21 +229,21 @@ Timer = {
--- Stop the timer. --- Stop the timer.
-- @function myTimer:Stop -- @function myTimer:Stop
Stop = function(t) Stop = function(t)
LevelVars.__TEN_timer.timers[t.name].active = false LevelVars.Engine.Timer.timers[t.name].active = false
end; end;
--- Get whether or not the timer is active --- Get whether or not the timer is active
-- @function myTimer:IsActive -- @function myTimer:IsActive
-- @return true if the timer is active, false if otherwise -- @return true if the timer is active, false if otherwise
IsActive = function(t) IsActive = function(t)
return LevelVars.__TEN_timer.timers[t.name].active return LevelVars.Engine.Timer.timers[t.name].active
end; end;
--- Pause or unpause the timer. If showing the remaining time on-screen, its color will be set to yellow (paused) or white (unpaused). --- Pause or unpause the timer. If showing the remaining time on-screen, its color will be set to yellow (paused) or white (unpaused).
-- @function myTimer:SetPaused -- @function myTimer:SetPaused
-- @bool p if true, the timer will be paused; if false, it would be unpaused -- @bool p if true, the timer will be paused; if false, it would be unpaused
SetPaused = function(t, p) SetPaused = function(t, p)
local thisTimer = LevelVars.__TEN_timer.timers[t.name] local thisTimer = LevelVars.Engine.Timer.timers[t.name]
thisTimer.paused = p thisTimer.paused = p
if thisTimer.timerFormat then if thisTimer.timerFormat then
if p then if p then
@ -256,21 +258,21 @@ Timer = {
-- @function myTimer:IsPaused -- @function myTimer:IsPaused
-- @return true if the timer is paused, false if otherwise -- @return true if the timer is paused, false if otherwise
IsPaused = function(t) IsPaused = function(t)
return LevelVars.__TEN_timer.timers[t.name].paused return LevelVars.Engine.Timer.timers[t.name].paused
end; end;
--- Get the remaining time for a timer. --- Get the remaining time for a timer.
-- @function myTimer:GetRemainingTime -- @function myTimer:GetRemainingTime
-- @return the time in seconds remaining on the clock -- @return the time in seconds remaining on the clock
GetRemainingTime = function(t) GetRemainingTime = function(t)
return LevelVars.__TEN_timer.timers[t.name].remainingTime return LevelVars.Engine.Timer.timers[t.name].remainingTime
end; end;
--- Set the remaining time for a timer --- Set the remaining time for a timer
-- @function myTimer:SetRemainingTime -- @function myTimer:SetRemainingTime
-- @number remainingTime the new time remaining for the timer -- @number remainingTime the new time remaining for the timer
SetRemainingTime = function(t, remainingTime) SetRemainingTime = function(t, remainingTime)
LevelVars.__TEN_timer.timers[t.name].remainingTime = remainingTime LevelVars.Engine.Timer.timers[t.name].remainingTime = remainingTime
end; end;
--- Get the total time for a timer. --- Get the total time for a timer.
@ -278,23 +280,31 @@ Timer = {
-- @function myTimer:GetRemainingTime -- @function myTimer:GetRemainingTime
-- @return the timer's total time -- @return the timer's total time
GetTotalTime = function(t) GetTotalTime = function(t)
return LevelVars.__TEN_timer.timers[t.name].totalTime return LevelVars.Engine.Timer.timers[t.name].totalTime
end; end;
--- Set the total time for a timer --- Set the total time for a timer
-- @function myTimer:SetTotalTime -- @function myTimer:SetTotalTime
-- @number totalTime timer's new total time -- @number totalTime timer's new total time
SetTotalTime = function(t, totalTime) SetTotalTime = function(t, totalTime)
LevelVars.__TEN_timer.timers[t.name].totalTime = totalTime LevelVars.Engine.Timer.timers[t.name].totalTime = totalTime
end; end;
--- Set whether or not the timer loops --- Set whether or not the timer loops
-- @function myTimer:SetLooping -- @function myTimer:SetLooping
-- @bool looping whether or not the timer loops -- @bool looping whether or not the timer loops
SetLooping = function(t, looping) SetLooping = function(t, looping)
LevelVars.__TEN_timer.timers[t.name].loop = looping LevelVars.Engine.Timer.timers[t.name].loop = looping
end; end;
} }
LevelFuncs.Engine.Timer.UpdateAll = function(dt)
for _, t in pairs(LevelVars.Engine.Timer.timers) do
Timer.Update(t, dt)
end
end
TEN.Logic.AddCallback(TEN.Logic.CallbackPoint.PRECONTROLPHASE, LevelFuncs.Engine.Timer.UpdateAll)
return Timer return Timer

View file

@ -1,7 +1,7 @@
-- New level script file. -- New level script file.
-- To include other script files, you can use require("filename") command. -- To include other script files, you can use require("filename") command.
local Util = require("Util") local Util = require("Engine.Util")
Util.ShortenTENCalls() Util.ShortenTENCalls()
-- Called when entering a level, either after leveljump, new game or loading game -- Called when entering a level, either after leveljump, new game or loading game
@ -27,4 +27,4 @@ LevelFuncs.OnEnd = function() end
LevelFuncs.PrintText = function(Triggerer, Argument) LevelFuncs.PrintText = function(Triggerer, Argument)
local TestText = DisplayString(Argument, 100, 100, Color.new(250,250,250)) local TestText = DisplayString(Argument, 100, 100, Color.new(250,250,250))
ShowString(TestText, 1) ShowString(TestText, 1)
end end

View file

@ -1,10 +1,10 @@
-- Title script file -- Title script file
local Util = require("Util") local Util = require("Engine.Util")
Util.ShortenTENCalls() Util.ShortenTENCalls()
LevelFuncs.OnLoad = function() end LevelFuncs.OnLoad = function() end
LevelFuncs.OnSave = function() end LevelFuncs.OnSave = function() end
LevelFuncs.OnEnd = function() end LevelFuncs.OnEnd = function() end
LevelFuncs.OnStart = function() end
LevelFuncs.OnControlPhase = function() end LevelFuncs.OnControlPhase = function(dt) end

View file

@ -570,7 +570,9 @@ void LaraGun(ItemInfo* laraItem)
{ {
if (!GetAmmo(laraItem, lara->Control.Weapon.GunType)) if (!GetAmmo(laraItem, lara->Control.Weapon.GunType))
{ {
lara->Control.Weapon.RequestGunType = Objects[ID_PISTOLS_ITEM].loaded ? LaraWeaponType::Pistol : LaraWeaponType::None; bool hasPistols = lara->Weapons[(int)LaraWeaponType::Pistol].Present && Objects[ID_PISTOLS_ITEM].loaded;
lara->Control.Weapon.RequestGunType = hasPistols ? LaraWeaponType::Pistol : LaraWeaponType::None;
return; return;
} }
} }
@ -1186,7 +1188,7 @@ HolsterSlot HolsterSlotForWeapon(LaraWeaponType weaponType)
return HolsterSlot::Harpoon; return HolsterSlot::Harpoon;
case LaraWeaponType::Crossbow: case LaraWeaponType::Crossbow:
return HolsterSlot::Crowssbow; return HolsterSlot::Crossbow;
case LaraWeaponType::GrenadeLauncher: case LaraWeaponType::GrenadeLauncher:
return HolsterSlot::GrenadeLauncher; return HolsterSlot::GrenadeLauncher;

View file

@ -436,8 +436,12 @@ void UndrawShotgunMeshes(ItemInfo* laraItem, LaraWeaponType weaponType)
{ {
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
lara->Control.Weapon.HolsterInfo.BackHolster = HolsterSlotForWeapon(weaponType);
lara->MeshPtrs[LM_RHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_RHAND; lara->MeshPtrs[LM_RHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_RHAND;
if (lara->Weapons[(int)weaponType].Present)
lara->Control.Weapon.HolsterInfo.BackHolster = HolsterSlotForWeapon(weaponType);
else
lara->Control.Weapon.HolsterInfo.BackHolster = HolsterSlot::Empty;
} }
void FireHarpoon(ItemInfo* laraItem) void FireHarpoon(ItemInfo* laraItem)
@ -621,10 +625,6 @@ void HarpoonBoltControl(short itemNumber)
auto pos = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0); auto pos = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0);
TriggerShockwave(&pos, 40, 176, 64, 0, 96, 128, 16, 0, 0); TriggerShockwave(&pos, 40, 176, 64, 0, 96, 128, 16, 0, 0);
ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0); ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = item->RoomNumber;
SmashedMesh[SmashedMeshCount] = currentMesh;
SmashedMeshCount++;
currentMesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
} }
} }
@ -1009,10 +1009,6 @@ void GrenadeControl(short itemNumber)
auto pos = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0); auto pos = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0);
TriggerShockwave(&pos, 40, 176, 64, 0, 96, 128, 16, 0, 0); TriggerShockwave(&pos, 40, 176, 64, 0, 96, 128, 16, 0, 0);
ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0); ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = item->RoomNumber;
SmashedMesh[SmashedMeshCount] = currentMesh;
SmashedMeshCount++;
currentMesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
} }
} }
@ -1316,10 +1312,6 @@ void RocketControl(short itemNumber)
auto pose = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0); auto pose = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0);
TriggerShockwave(&pose, 40, 176, 64, 0, 96, 128, 16, 0, 0); TriggerShockwave(&pose, 40, 176, 64, 0, 96, 128, 16, 0, 0);
ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0); ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = item->RoomNumber;
SmashedMesh[SmashedMeshCount] = currentMesh;
SmashedMeshCount++;
currentMesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
} }
} }
@ -1598,13 +1590,7 @@ void CrossbowBoltControl(short itemNumber)
{ {
currentMesh->HitPoints -= Weapons[(int)LaraWeaponType::Crossbow].Damage; currentMesh->HitPoints -= Weapons[(int)LaraWeaponType::Crossbow].Damage;
if (currentMesh->HitPoints <= 0) if (currentMesh->HitPoints <= 0)
{
ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0); ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = item->RoomNumber;
SmashedMesh[SmashedMeshCount] = currentMesh;
SmashedMeshCount++;
currentMesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
}
} }
k++; k++;

View file

@ -891,7 +891,7 @@ enum class HolsterSlot
Shotgun = ID_SHOTGUN_ANIM, Shotgun = ID_SHOTGUN_ANIM,
HK = ID_HK_ANIM, HK = ID_HK_ANIM,
Harpoon = ID_HARPOON_ANIM, Harpoon = ID_HARPOON_ANIM,
Crowssbow = ID_CROSSBOW_ANIM, Crossbow = ID_CROSSBOW_ANIM,
GrenadeLauncher = ID_GRENADE_ANIM, GrenadeLauncher = ID_GRENADE_ANIM,
RocketLauncher = ID_ROCKET_ANIM RocketLauncher = ID_ROCKET_ANIM
}; };

View file

@ -490,7 +490,10 @@ void UndrawPistolMeshRight(ItemInfo* laraItem, LaraWeaponType weaponType)
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
lara->MeshPtrs[LM_RHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_RHAND; lara->MeshPtrs[LM_RHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_RHAND;
lara->Control.Weapon.HolsterInfo.RightHolster = HolsterSlotForWeapon(weaponType); if (lara->Weapons[(int)weaponType].Present)
lara->Control.Weapon.HolsterInfo.RightHolster = HolsterSlotForWeapon(weaponType);
else
lara->Control.Weapon.HolsterInfo.RightHolster = HolsterSlot::Empty;
} }
void UndrawPistolMeshLeft(ItemInfo* laraItem, LaraWeaponType weaponType) void UndrawPistolMeshLeft(ItemInfo* laraItem, LaraWeaponType weaponType)
@ -500,6 +503,10 @@ void UndrawPistolMeshLeft(ItemInfo* laraItem, LaraWeaponType weaponType)
if (weaponType != LaraWeaponType::Revolver) if (weaponType != LaraWeaponType::Revolver)
{ {
lara->MeshPtrs[LM_LHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_LHAND; lara->MeshPtrs[LM_LHAND] = Objects[ID_LARA_SKIN].meshIndex + LM_LHAND;
lara->Control.Weapon.HolsterInfo.LeftHolster = HolsterSlotForWeapon(weaponType);
if (lara->Weapons[(int)weaponType].Present)
lara->Control.Weapon.HolsterInfo.LeftHolster = HolsterSlotForWeapon(weaponType);
else
lara->Control.Weapon.HolsterInfo.LeftHolster = HolsterSlot::Empty;
} }
} }

View file

@ -476,7 +476,9 @@ BOUNDING_BOX* GetBoundsAccurate(ItemInfo* item)
InterpolatedBounds.Y2 = framePtr[0]->boundingBox.Y2 + (framePtr[1]->boundingBox.Y2 - framePtr[0]->boundingBox.Y2) * frac / rate; InterpolatedBounds.Y2 = framePtr[0]->boundingBox.Y2 + (framePtr[1]->boundingBox.Y2 - framePtr[0]->boundingBox.Y2) * frac / rate;
InterpolatedBounds.Z1 = framePtr[0]->boundingBox.Z1 + (framePtr[1]->boundingBox.Z1 - framePtr[0]->boundingBox.Z1) * frac / rate; InterpolatedBounds.Z1 = framePtr[0]->boundingBox.Z1 + (framePtr[1]->boundingBox.Z1 - framePtr[0]->boundingBox.Z1) * frac / rate;
InterpolatedBounds.Z2 = framePtr[0]->boundingBox.Z2 + (framePtr[1]->boundingBox.Z2 - framePtr[0]->boundingBox.Z2) * frac / rate; InterpolatedBounds.Z2 = framePtr[0]->boundingBox.Z2 + (framePtr[1]->boundingBox.Z2 - framePtr[0]->boundingBox.Z2) * frac / rate;
return &InterpolatedBounds; {
return &InterpolatedBounds;
}
} }
} }

View file

@ -271,10 +271,10 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision
{ {
auto* framePtr = GetBestFrame(laraItem); auto* framePtr = GetBestFrame(laraItem);
if (item->Pose.Position.y + GlobalCollisionBounds.Y2 <= laraItem->Pose.Position.y + framePtr->boundingBox.Y1) if ((item->Pose.Position.y + GlobalCollisionBounds.Y2) <= (laraItem->Pose.Position.y + framePtr->boundingBox.Y1))
return false; return false;
if (item->Pose.Position.y + GlobalCollisionBounds.Y1 >= framePtr->boundingBox.Y2) if ((item->Pose.Position.y + GlobalCollisionBounds.Y1) >= framePtr->boundingBox.Y2)
return false; return false;
float sinY = phd_sin(item->Pose.Orientation.y); float sinY = phd_sin(item->Pose.Orientation.y);
@ -286,10 +286,10 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision
int x = (dx * cosY) - (dz * sinY); int x = (dx * cosY) - (dz * sinY);
int z = (dz * cosY) + (dx * sinY); int z = (dz * cosY) + (dx * sinY);
if (x < GlobalCollisionBounds.X1 - coll->Setup.Radius || if (x < (GlobalCollisionBounds.X1 - coll->Setup.Radius) ||
x > GlobalCollisionBounds.X2 + coll->Setup.Radius || x > (GlobalCollisionBounds.X2 + coll->Setup.Radius) ||
z < GlobalCollisionBounds.Z1 - coll->Setup.Radius || z < (GlobalCollisionBounds.Z1 - coll->Setup.Radius) ||
z > GlobalCollisionBounds.Z2 + coll->Setup.Radius) z > (GlobalCollisionBounds.Z2 + coll->Setup.Radius))
{ {
return false; return false;
} }
@ -331,7 +331,7 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
continue; continue;
} }
if (phd_Distance(&item->Pose, &item2->Pose) < COLLISION_CHECK_DISTANCE) if (Vector3Int::Distance(item->Pose.Position, item2->Pose.Position) < COLLISION_CHECK_DISTANCE)
{ {
auto box = TO_DX_BBOX(item2->Pose, GetBoundsAccurate(item2)); auto box = TO_DX_BBOX(item2->Pose, GetBoundsAccurate(item2));
float distance; float distance;
@ -346,16 +346,14 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
itemNumber = item2->NextItem; itemNumber = item2->NextItem;
} }
for (int j = 0; j < g_Level.Rooms[i].mesh.size(); j++) for (auto& mesh : g_Level.Rooms[i].mesh)
{ {
auto* mesh = &g_Level.Rooms[i].mesh[j]; if (!(mesh.flags & StaticMeshFlags::SM_VISIBLE))
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue; continue;
if (phd_Distance(&item->Pose, &mesh->pos) < COLLISION_CHECK_DISTANCE) if (phd_Distance(&item->Pose, &mesh.pos) < COLLISION_CHECK_DISTANCE)
{ {
auto box = TO_DX_BBOX(mesh->pos, GetBoundsAccurate(mesh, false)); auto box = TO_DX_BBOX(mesh.pos, GetBoundsAccurate(&mesh, false));
float distance; float distance;
if (box.Intersects(origin, direction, distance) && distance < coll->Setup.Radius * 2) if (box.Intersects(origin, direction, distance) && distance < coll->Setup.Radius * 2)
@ -453,7 +451,7 @@ bool MoveLaraPosition(Vector3Int* offset, ItemInfo* item, ItemInfo* laraItem)
{ {
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
Matrix matrix = Matrix::CreateFromYawPitchRoll( auto matrix = Matrix::CreateFromYawPitchRoll(
TO_RAD(item->Pose.Orientation.y), TO_RAD(item->Pose.Orientation.y),
TO_RAD(item->Pose.Orientation.x), TO_RAD(item->Pose.Orientation.x),
TO_RAD(item->Pose.Orientation.z) TO_RAD(item->Pose.Orientation.z)
@ -491,12 +489,12 @@ static bool ItemInRange(int x, int z, int radius)
return ((SQUARE(x) + SQUARE(z)) <= SQUARE(radius)); return ((SQUARE(x) + SQUARE(z)) <= SQUARE(radius));
} }
bool ItemNearLara(PHD_3DPOS* pos, int radius) bool ItemNearLara(Vector3Int* origin, int radius)
{ {
auto target = GameVector( auto target = GameVector(
pos->Position.x - LaraItem->Pose.Position.x, origin->x - LaraItem->Pose.Position.x,
pos->Position.y - LaraItem->Pose.Position.y, origin->y - LaraItem->Pose.Position.y,
pos->Position.z - LaraItem->Pose.Position.z origin->z - LaraItem->Pose.Position.z
); );
if (!ItemCollide(target.y, ITEM_RADIUS_YMAX)) if (!ItemCollide(target.y, ITEM_RADIUS_YMAX))
@ -515,9 +513,9 @@ bool ItemNearLara(PHD_3DPOS* pos, int radius)
return false; return false;
} }
bool ItemNearTarget(PHD_3DPOS* origin, ItemInfo* target, int radius) bool ItemNearTarget(Vector3Int* origin, ItemInfo* targetEntity, int radius)
{ {
auto pos = origin->Position - target->Pose.Position; auto pos = *origin - targetEntity->Pose.Position;
if (!ItemCollide(pos.y, ITEM_RADIUS_YMAX)) if (!ItemCollide(pos.y, ITEM_RADIUS_YMAX))
return false; return false;
@ -528,31 +526,31 @@ bool ItemNearTarget(PHD_3DPOS* origin, ItemInfo* target, int radius)
if (!ItemInRange(pos.x, pos.z, radius)) if (!ItemInRange(pos.x, pos.z, radius))
return false; return false;
auto* bounds = GetBoundsAccurate(target); auto* bounds = GetBoundsAccurate(targetEntity);
if (pos.y >= bounds->Y1 && pos.y <= bounds->Y2) if (pos.y >= bounds->Y1 && pos.y <= bounds->Y2)
return true; return true;
return false; return false;
} }
bool Move3DPosTo3DPos(PHD_3DPOS* origin, PHD_3DPOS* target, int velocity, short angleAdd) bool Move3DPosTo3DPos(PHD_3DPOS* fromPose, PHD_3DPOS* toPose, int velocity, short angleAdd)
{ {
auto direction = target->Position - origin->Position; auto direction = toPose->Position - fromPose->Position;
int distance = sqrt(SQUARE(direction.x) + SQUARE(direction.y) + SQUARE(direction.z)); float distance = Vector3Int::Distance(fromPose->Position, toPose->Position);
if (velocity < distance) if (velocity < distance)
origin->Position += direction * velocity / distance; fromPose->Position += direction * (velocity / distance);
else else
origin->Position = target->Position; fromPose->Position = toPose->Position;
if (!Lara.Control.IsMoving) if (!Lara.Control.IsMoving)
{ {
bool shouldAnimate = (distance - velocity) > (velocity * ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD); bool shouldAnimate = ((distance - velocity) > (velocity * ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD));
if (shouldAnimate && Lara.Control.WaterStatus != WaterStatus::Underwater) if (shouldAnimate && Lara.Control.WaterStatus != WaterStatus::Underwater)
{ {
int angle = mGetAngle(target->Position.x, target->Position.z, origin->Position.x, origin->Position.z); int angle = mGetAngle(toPose->Position.x, toPose->Position.z, fromPose->Position.x, fromPose->Position.z);
int direction = (GetQuadrant(angle) - GetQuadrant(target->Orientation.y)) & 3; int direction = (GetQuadrant(angle) - GetQuadrant(toPose->Orientation.y)) & 3;
switch (direction) switch (direction)
{ {
@ -583,31 +581,31 @@ bool Move3DPosTo3DPos(PHD_3DPOS* origin, PHD_3DPOS* target, int velocity, short
Lara.Control.Count.PositionAdjust = 0; Lara.Control.Count.PositionAdjust = 0;
} }
short deltaAngle = target->Orientation.x - origin->Orientation.x; short deltaAngle = toPose->Orientation.x - fromPose->Orientation.x;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.x += angleAdd; fromPose->Orientation.x += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.x -= angleAdd; fromPose->Orientation.x -= angleAdd;
else else
origin->Orientation.x = target->Orientation.x; fromPose->Orientation.x = toPose->Orientation.x;
deltaAngle = target->Orientation.y - origin->Orientation.y; deltaAngle = toPose->Orientation.y - fromPose->Orientation.y;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.y += angleAdd; fromPose->Orientation.y += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.y -= angleAdd; fromPose->Orientation.y -= angleAdd;
else else
origin->Orientation.y = target->Orientation.y; fromPose->Orientation.y = toPose->Orientation.y;
deltaAngle = target->Orientation.z - origin->Orientation.z; deltaAngle = toPose->Orientation.z - fromPose->Orientation.z;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.z += angleAdd; fromPose->Orientation.z += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.z -= angleAdd; fromPose->Orientation.z -= angleAdd;
else else
origin->Orientation.z = target->Orientation.z; fromPose->Orientation.z = toPose->Orientation.z;
return (origin->Position == target->Position && origin->Orientation == target->Orientation); return (fromPose->Position == toPose->Position && fromPose->Orientation == toPose->Orientation);
} }
bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius) bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
@ -626,13 +624,13 @@ bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
int x = laraItem->Pose.Position.x - item->Pose.Position.x; int x = laraItem->Pose.Position.x - item->Pose.Position.x;
int z = laraItem->Pose.Position.z - item->Pose.Position.z; int z = laraItem->Pose.Position.z - item->Pose.Position.z;
int dx = (cosY * x) - (sinY * z); int dx = (x * cosY) - (z * sinY);
int dz = (cosY * z) + (sinY * x); int dz = (z * cosY) + (x * sinY);
if (dx >= bounds->X1 - radius && if (dx >= (bounds->X1 - radius) &&
dx <= radius + bounds->X2 && dx <= (radius + bounds->X2) &&
dz >= bounds->Z1 - radius && dz >= (bounds->Z1 - radius) &&
dz <= radius + bounds->Z2) dz <= (radius + bounds->Z2))
{ {
return true; return true;
} }
@ -688,11 +686,7 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
int rx = (dx * cosY) - (dz * sinY); int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY); int rz = (dz * cosY) + (dx * sinY);
BOUNDING_BOX* bounds; auto* bounds = (bigPush & 2) ? &GlobalCollisionBounds : (BOUNDING_BOX*)GetBestFrame(item);
if (bigPush & 2)
bounds = &GlobalCollisionBounds;
else
bounds = (BOUNDING_BOX*)GetBestFrame(item);
int minX = bounds->X1; int minX = bounds->X1;
int maxX = bounds->X2; int maxX = bounds->X2;
@ -729,8 +723,8 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
else else
rz -= bottom; rz -= bottom;
item2->Pose.Position.x = item->Pose.Position.x + cosY * rx + sinY * rz; item2->Pose.Position.x = item->Pose.Position.x + (rx * cosY) + (rz * sinY);
item2->Pose.Position.z = item->Pose.Position.z + cosY * rz - sinY * rx; item2->Pose.Position.z = item->Pose.Position.z + (rz * cosY) - (rx * sinY);
auto* lara = item2->IsLara() ? GetLaraInfo(item2) : nullptr; auto* lara = item2->IsLara() ? GetLaraInfo(item2) : nullptr;
@ -755,12 +749,12 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
coll->Setup.LowerCeilingBound = 0; coll->Setup.LowerCeilingBound = 0;
coll->Setup.UpperCeilingBound = MAX_HEIGHT; coll->Setup.UpperCeilingBound = MAX_HEIGHT;
auto facing = coll->Setup.ForwardAngle; auto headingAngle = coll->Setup.ForwardAngle;
coll->Setup.ForwardAngle = phd_atan(item2->Pose.Position.z - coll->Setup.OldPosition.z, item2->Pose.Position.x - coll->Setup.OldPosition.x); coll->Setup.ForwardAngle = phd_atan(item2->Pose.Position.z - coll->Setup.OldPosition.z, item2->Pose.Position.x - coll->Setup.OldPosition.x);
GetCollisionInfo(coll, item2); GetCollisionInfo(coll, item2);
coll->Setup.ForwardAngle = facing; coll->Setup.ForwardAngle = headingAngle;
if (coll->CollisionType == CT_NONE) if (coll->CollisionType == CT_NONE)
{ {
@ -778,8 +772,8 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
// If Lara is in the process of aligning to an object, cancel it. // If Lara is in the process of aligning to an object, cancel it.
if (lara != nullptr && lara->Control.Count.PositionAdjust > (LARA_POSITION_ADJUST_MAX_TIME / 6)) if (lara != nullptr && lara->Control.Count.PositionAdjust > (LARA_POSITION_ADJUST_MAX_TIME / 6))
{ {
Lara.Control.IsMoving = false; lara->Control.IsMoving = false;
Lara.Control.HandStatus = HandStatus::Free; lara->Control.HandStatus = HandStatus::Free;
} }
return true; return true;
@ -868,7 +862,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
{ {
auto* mesh = &g_Level.Rooms[i].mesh[j]; auto* mesh = &g_Level.Rooms[i].mesh[j];
// Only process meshes which are visible and solid // Only process meshes which are visible and solid.
if ((mesh->flags & StaticMeshFlags::SM_VISIBLE) && (mesh->flags & StaticMeshFlags::SM_SOLID)) if ((mesh->flags & StaticMeshFlags::SM_VISIBLE) && (mesh->flags & StaticMeshFlags::SM_SOLID))
{ {
if (phd_Distance(&item->Pose, &mesh->pos) < COLLISION_CHECK_DISTANCE) if (phd_Distance(&item->Pose, &mesh->pos) < COLLISION_CHECK_DISTANCE)
@ -882,39 +876,39 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
} }
} }
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, CollisionInfo* coll) bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pose, CollisionInfo* coll)
{ {
bool result = false; bool result = false;
// Get DX static bounds in global coords // Get DX static bounds in global coordinates.
auto staticBounds = TO_DX_BBOX(pos, box); auto staticBounds = TO_DX_BBOX(pose, box);
// Get local TR bounds and DX item bounds in global coords // Get local TR bounds and DX item bounds in global coordinates.
auto itemBBox = GetBoundsAccurate(item); auto itemBBox = GetBoundsAccurate(item);
auto itemBounds = TO_DX_BBOX(item->Pose, itemBBox); auto itemBounds = TO_DX_BBOX(item->Pose, itemBBox);
// Extend bounds a bit for visual testing // Extend bounds a bit for visual testing.
itemBounds.Extents = itemBounds.Extents + Vector3(WALL_SIZE); itemBounds.Extents = itemBounds.Extents + Vector3(SECTOR(1));
// Filter out any further checks if static isn't nearby // Filter out any further checks if static isn't nearby.
if (!staticBounds.Intersects(itemBounds)) if (!staticBounds.Intersects(itemBounds))
return false; return false;
// Bring back extents // Bring back extents.
itemBounds.Extents = itemBounds.Extents - Vector3(WALL_SIZE); itemBounds.Extents = itemBounds.Extents - Vector3(SECTOR(1));
// Draw static bounds // Draw static bounds.
g_Renderer.AddDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS); g_Renderer.AddDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
// Calculate horizontal item coll bounds according to radius // Calculate horizontal item collision bounds according to radius.
BOUNDING_BOX collBox; BOUNDING_BOX collBox;
collBox.X1 = -coll->Setup.Radius; collBox.X1 = -coll->Setup.Radius;
collBox.X2 = coll->Setup.Radius; collBox.X2 = coll->Setup.Radius;
collBox.Z1 = -coll->Setup.Radius; collBox.Z1 = -coll->Setup.Radius;
collBox.Z2 = coll->Setup.Radius; collBox.Z2 = coll->Setup.Radius;
// Calculate vertical item coll bounds according to either height (land mode) or precise bounds (water mode). // Calculate vertical item collision bounds according to either height (land mode) or precise bounds (water mode).
// Water mode needs special processing because height calc in original engines is inconsistent in such cases. // Water mode needs special processing because height calculation in original engines is inconsistent in such cases.
if (TestEnvironment(ENV_FLAG_WATER, item)) if (TestEnvironment(ENV_FLAG_WATER, item))
{ {
collBox.Y1 = itemBBox->Y1; collBox.Y1 = itemBBox->Y1;
@ -926,18 +920,18 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
collBox.Y2 = 0; collBox.Y2 = 0;
} }
// Get and test DX item coll bounds // Get and test DX item collision bounds.
auto collBounds = TO_DX_BBOX(PHD_3DPOS(item->Pose.Position), &collBox); auto collBounds = TO_DX_BBOX(PHD_3DPOS(item->Pose.Position), &collBox);
bool intersects = staticBounds.Intersects(collBounds); bool intersects = staticBounds.Intersects(collBounds);
// Check if previous item horizontal position intersects bounds // Check if previous item horizontal position intersects bounds.
auto oldCollBounds = TO_DX_BBOX(PHD_3DPOS(coll->Setup.OldPosition.x, item->Pose.Position.y, coll->Setup.OldPosition.z), &collBox); auto oldCollBounds = TO_DX_BBOX(PHD_3DPOS(coll->Setup.OldPosition.x, item->Pose.Position.y, coll->Setup.OldPosition.z), &collBox);
bool oldHorIntersects = staticBounds.Intersects(oldCollBounds); bool oldHorIntersects = staticBounds.Intersects(oldCollBounds);
// Draw item coll bounds // Draw item coll bounds.
g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS); g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
// Decompose static bounds into top/bottom plane vertices // Decompose static bounds into top/bottom plane vertices.
Vector3 corners[8]; Vector3 corners[8];
staticBounds.GetCorners(corners); staticBounds.GetCorners(corners);
Vector3 planeVertices[4][3] = Vector3 planeVertices[4][3] =
@ -948,7 +942,7 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
{ corners[3], corners[6], corners[2] } { corners[3], corners[6], corners[2] }
}; };
// Determine collision box vertical dimensions // Determine collision box vertical dimensions.
auto height = collBox.Height(); auto height = collBox.Height();
auto center = item->Pose.Position.y - height / 2; auto center = item->Pose.Position.y - height / 2;
@ -960,24 +954,23 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
// Calculate ray direction // Calculate ray direction.
auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(item->Pose.Orientation.y), TO_RAD(item->Pose.Orientation.x + (ANGLE(90 * i))), 0); auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(item->Pose.Orientation.y), TO_RAD(item->Pose.Orientation.x + (ANGLE(90 * i))), 0);
auto mxT = Matrix::CreateTranslation(Vector3::UnitY); auto mxT = Matrix::CreateTranslation(Vector3::UnitY);
auto direction = (mxT * mxR).Translation(); auto direction = (mxT * mxR).Translation();
// Make a ray and do ray tests against all decomposed planes // Make a ray and do ray tests against all decomposed planes.
auto ray = Ray(collBounds.Center, direction); auto ray = Ray(collBounds.Center, direction);
// Determine if top/bottom planes are closest ones or not // Determine if top/bottom planes are closest ones or not.
for (int p = 0; p < 4; p++) for (int p = 0; p < 4; p++)
{ {
// No plane intersection, quickly discard // No plane intersection, quickly discard.
float d = 0.0f; float d = 0.0f;
if (!ray.Intersects(planeVertices[p][0], planeVertices[p][1], planeVertices[p][2], d)) if (!ray.Intersects(planeVertices[p][0], planeVertices[p][1], planeVertices[p][2], d))
continue; continue;
// Process plane intersection only if distance is smaller // Process plane intersection only if distance is smaller than already found minimum.
// than already found minimum
if (d < minDistance) if (d < minDistance)
{ {
closestRay = ray; closestRay = ray;
@ -987,24 +980,25 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
} }
} }
if (closestPlane != -1) // Top/bottom plane found // Top/bottom plane found.
if (closestPlane != -1)
{ {
auto bottom = closestPlane >= 2; auto bottom = closestPlane >= 2;
auto yPoint = abs((closestRay.direction * minDistance).y); auto yPoint = abs((closestRay.direction * minDistance).y);
auto distanceToVerticalPlane = height / 2 - yPoint; auto distanceToVerticalPlane = height / 2 - yPoint;
// Correct position according to top/bottom bounds, if collided and plane is nearby // Correct position according to top/bottom bounds, if collided and plane is nearby.
if (intersects && oldHorIntersects && minDistance < height) if (intersects && oldHorIntersects && minDistance < height)
{ {
if (bottom) if (bottom)
{ {
// HACK: additionally subtract 2 from bottom plane, or else false positives may occur. // HACK: Additionally subtract 2 from bottom plane, otherwise false positives may occur.
item->Pose.Position.y += distanceToVerticalPlane + 2; item->Pose.Position.y += distanceToVerticalPlane + 2;
coll->CollisionType = CT_TOP; coll->CollisionType = CT_TOP;
} }
else else
{ {
// Set collision type only if dry room (in water rooms it causes stucking) // Set collision type only if dry room (in water rooms the player can get stuck).
item->Pose.Position.y -= distanceToVerticalPlane; item->Pose.Position.y -= distanceToVerticalPlane;
coll->CollisionType = (g_Level.Rooms[item->RoomNumber].flags & 1) ? coll->CollisionType : CT_CLAMP; coll->CollisionType = (g_Level.Rooms[item->RoomNumber].flags & 1) ? coll->CollisionType : CT_CLAMP;
} }
@ -1020,29 +1014,29 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
if (!intersects) if (!intersects)
return false; return false;
// Check if bounds still collide after top/bottom position correction // Check if bounds still collide after top/bottom position correction.
if (!staticBounds.Intersects(TO_DX_BBOX(PHD_3DPOS(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z), &collBox))) if (!staticBounds.Intersects(TO_DX_BBOX(PHD_3DPOS(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z), &collBox)))
return result; return result;
// Determine identity rotation/distance // Determine identity orientation/distance.
auto distance = Vector3(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); auto distance = (item->Pose.Position - pose.Position).ToVector3();
auto sinY = phd_sin(pos.Orientation.y); auto sinY = phd_sin(pose.Orientation.y);
auto cosY = phd_cos(pos.Orientation.y); auto cosY = phd_cos(pose.Orientation.y);
// Rotate item to collision bounds identity // Rotate item to collision bounds identity.
auto x = round(distance.x * cosY - distance.z * sinY) + pos.Position.x; auto x = round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x;
auto y = item->Pose.Position.y; auto y = item->Pose.Position.y;
auto z = round(distance.x * sinY + distance.z * cosY) + pos.Position.z; auto z = round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z;
// Determine identity static collision bounds // Determine identity static collision bounds.
auto XMin = pos.Position.x + box->X1; auto XMin = pose.Position.x + box->X1;
auto XMax = pos.Position.x + box->X2; auto XMax = pose.Position.x + box->X2;
auto YMin = pos.Position.y + box->Y1; auto YMin = pose.Position.y + box->Y1;
auto YMax = pos.Position.y + box->Y2; auto YMax = pose.Position.y + box->Y2;
auto ZMin = pos.Position.z + box->Z1; auto ZMin = pose.Position.z + box->Z1;
auto ZMax = pos.Position.z + box->Z2; auto ZMax = pose.Position.z + box->Z2;
// Determine item collision bounds // Determine item collision bounds.
auto inXMin = x + collBox.X1; auto inXMin = x + collBox.X1;
auto inXMax = x + collBox.X2; auto inXMax = x + collBox.X2;
auto inYMin = y + collBox.Y1; auto inYMin = y + collBox.Y1;
@ -1050,15 +1044,15 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
auto inZMin = z + collBox.Z1; auto inZMin = z + collBox.Z1;
auto inZMax = z + collBox.Z2; auto inZMax = z + collBox.Z2;
// Don't calculate shifts if not in bounds // Don't calculate shifts if not in bounds.
if (inXMax <= XMin || inXMin >= XMax || if (inXMax <= XMin || inXMin >= XMax ||
inYMax <= YMin || inYMin >= YMax || inYMax <= YMin || inYMin >= YMax ||
inZMax <= ZMin || inZMin >= ZMax) inZMax <= ZMin || inZMin >= ZMax)
return result; return result;
// Calculate shifts // Calculate shifts.
Vector3Int rawShift = {}; auto rawShift = Vector3Int::Zero;
auto shiftLeft = inXMax - XMin; auto shiftLeft = inXMax - XMin;
auto shiftRight = XMax - inXMin; auto shiftRight = XMax - inXMin;
@ -1076,13 +1070,13 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
else else
rawShift.z = shiftRight; rawShift.z = shiftRight;
// Rotate previous collision position to identity // Rotate previous collision position to identity.
distance = Vector3(coll->Setup.OldPosition.x, coll->Setup.OldPosition.y, coll->Setup.OldPosition.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); distance = (coll->Setup.OldPosition - pose.Position).ToVector3();
auto ox = round(distance.x * cosY - distance.z * sinY) + pos.Position.x; auto ox = round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x;
auto oz = round(distance.x * sinY + distance.z * cosY) + pos.Position.z; auto oz = round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z;
// Calculate collisison type based on identity rotation // Calculate collisison type based on identity orientation.
switch (GetQuadrant(coll->Setup.ForwardAngle - pos.Orientation.y)) switch (GetQuadrant(coll->Setup.ForwardAngle - pose.Orientation.y))
{ {
case NORTH: case NORTH:
if (rawShift.x > coll->Setup.Radius || rawShift.x < -coll->Setup.Radius) if (rawShift.x > coll->Setup.Radius || rawShift.x < -coll->Setup.Radius)
@ -1173,19 +1167,19 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, Collis
break; break;
} }
// Determine final shifts rotation/distance // Determine final shifts orientation/distance.
distance = Vector3(x + coll->Shift.x, y, z + coll->Shift.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); distance = Vector3(x + coll->Shift.x, y, z + coll->Shift.z) - pose.Position.ToVector3();
sinY = phd_sin(-pos.Orientation.y); sinY = phd_sin(-pose.Orientation.y);
cosY = phd_cos(-pos.Orientation.y); cosY = phd_cos(-pose.Orientation.y);
// Calculate final shifts rotation/distance // Calculate final shifts orientation/distance.
coll->Shift.x = (round(distance.x * cosY - distance.z * sinY) + pos.Position.x) - item->Pose.Position.x; coll->Shift.x = (round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x) - item->Pose.Position.x;
coll->Shift.z = (round(distance.x * sinY + distance.z * cosY) + pos.Position.z) - item->Pose.Position.z; coll->Shift.z = (round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z) - item->Pose.Position.z;
if (coll->Shift.x == 0 && coll->Shift.z == 0) if (coll->Shift.x == 0 && coll->Shift.z == 0)
coll->CollisionType = CT_NONE; // Paranoid coll->CollisionType = CT_NONE; // Paranoid.
// Set splat state flag if item is Lara and bounds are taller than Lara's headroom // Set splat state flag if item is Lara and bounds are taller than Lara's headroom.
if (item == LaraItem && coll->CollisionType == CT_FRONT) if (item == LaraItem && coll->CollisionType == CT_FRONT)
coll->HitTallObject = (YMin <= inYMin + LARA_HEADROOM); coll->HitTallObject = (YMin <= inYMin + LARA_HEADROOM);
@ -1197,39 +1191,39 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto prevCollResult = GetCollision(x, y, z, item->RoomNumber); auto prevPointProbe = GetCollision(x, y, z, item->RoomNumber);
auto collResult = GetCollision(item); auto pointProbe = GetCollision(item);
auto* bounds = GetBoundsAccurate(item); auto* bounds = GetBoundsAccurate(item);
int radius = bounds->Height(); int radius = bounds->Height();
item->Pose.Position.y += radius; item->Pose.Position.y += radius;
if (item->Pose.Position.y >= collResult.Position.Floor) if (item->Pose.Position.y >= pointProbe.Position.Floor)
{ {
int bs = 0; int bs = 0;
if (collResult.Position.FloorSlope && prevCollResult.Position.Floor < collResult.Position.Floor) if (pointProbe.Position.FloorSlope && prevPointProbe.Position.Floor < pointProbe.Position.Floor)
{ {
int yAngle = (long)((unsigned short)item->Pose.Orientation.y); int yAngle = (long)((unsigned short)item->Pose.Orientation.y);
if (collResult.FloorTilt.x < 0) if (pointProbe.FloorTilt.x < 0)
{ {
if (yAngle >= ANGLE(180.0f)) if (yAngle >= ANGLE(180.0f))
bs = 1; bs = 1;
} }
else if (collResult.FloorTilt.x > 0) else if (pointProbe.FloorTilt.x > 0)
{ {
if (yAngle <= ANGLE(180.0f)) if (yAngle <= ANGLE(180.0f))
bs = 1; bs = 1;
} }
if (collResult.FloorTilt.y < 0) if (pointProbe.FloorTilt.y < 0)
{ {
if (yAngle >= ANGLE(90.0f) && yAngle <= ANGLE(270.0f)) if (yAngle >= ANGLE(90.0f) && yAngle <= ANGLE(270.0f))
bs = 1; bs = 1;
} }
else if (collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.y > 0)
{ {
if (yAngle <= ANGLE(90.0f) || yAngle >= ANGLE(270.0f)) if (yAngle <= ANGLE(90.0f) || yAngle >= ANGLE(270.0f))
bs = 1; bs = 1;
@ -1238,7 +1232,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
// If last position of item was also below this floor height, we've hit a wall, else we've hit a floor. // If last position of item was also below this floor height, we've hit a wall, else we've hit a floor.
if (y > (collResult.Position.Floor + 32) && bs == 0 && if (y > (pointProbe.Position.Floor + 32) && bs == 0 &&
(((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) || (((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) ||
((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1))))) ((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1)))))
{ {
@ -1246,8 +1240,8 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
long xs; long xs;
if ((x & (~(WALL_SIZE - 1))) != (item->Pose.Position.x & (~(WALL_SIZE - 1))) && // X crossed boundary? if ((x & (~WALL_MASK)) != (item->Pose.Position.x & (~WALL_MASK)) && // X crossed boundary?
(z & (~(WALL_SIZE - 1))) != (item->Pose.Position.z & (~(WALL_SIZE - 1)))) // Z crossed boundary as well? (z & (~WALL_MASK)) != (item->Pose.Position.z & (~WALL_MASK))) // Z crossed boundary as well?
{ {
if (abs(x - item->Pose.Position.x) < abs(z - item->Pose.Position.z)) if (abs(x - item->Pose.Position.x) < abs(z - item->Pose.Position.z))
xs = 1; // X has travelled the shortest, so (maybe) hit first. (Seems to work ok). xs = 1; // X has travelled the shortest, so (maybe) hit first. (Seems to work ok).
@ -1257,7 +1251,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
else else
xs = 1; xs = 1;
if ((x & (~(WALL_SIZE - 1))) != (item->Pose.Position.x & (~(WALL_SIZE - 1))) && xs) // X crossed boundary? if ((x & (~WALL_MASK)) != (item->Pose.Position.x & (~WALL_MASK)) && xs) // X crossed boundary?
{ {
// Hit angle = ANGLE(270.0f). // Hit angle = ANGLE(270.0f).
if (xv <= 0) if (xv <= 0)
@ -1278,14 +1272,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
item->Pose.Position.z = z; item->Pose.Position.z = z;
} }
// Hit a steep slope? // Hit a steep slope?
else if (collResult.Position.FloorSlope) else if (pointProbe.Position.FloorSlope)
{ {
// Need to know which direction the slope is. // Need to know which direction the slope is.
item->Animation.Velocity.z -= (item->Animation.Velocity.z / 4); item->Animation.Velocity.z -= (item->Animation.Velocity.z / 4);
// Hit angle = ANGLE(90.0f) // Hit angle = ANGLE(90.0f)
if (collResult.FloorTilt.x < 0 && ((abs(collResult.FloorTilt.x)) - (abs(collResult.FloorTilt.y)) >= 2)) if (pointProbe.FloorTilt.x < 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(180.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(180.0f))
{ {
@ -1297,7 +1291,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z -= collResult.FloorTilt.x * 2; item->Animation.Velocity.z -= pointProbe.FloorTilt.x * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(90.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(270.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(90.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(270.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1319,7 +1313,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(270.0f) // Hit angle = ANGLE(270.0f)
else if (collResult.FloorTilt.x > 0 && ((abs(collResult.FloorTilt.x)) - (abs(collResult.FloorTilt.y)) >= 2)) else if (pointProbe.FloorTilt.x > 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) < ANGLE(180.0f)) if (((unsigned short)item->Pose.Orientation.y) < ANGLE(180.0f))
{ {
@ -1331,7 +1325,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x * 2; item->Animation.Velocity.z += pointProbe.FloorTilt.x * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(270.0f) || (unsigned short)item->Pose.Orientation.y < ANGLE(90.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(270.0f) || (unsigned short)item->Pose.Orientation.y < ANGLE(90.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1353,7 +1347,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = 0 // Hit angle = 0
else if (collResult.FloorTilt.y < 0 && ((abs(collResult.FloorTilt.y)) - (abs(collResult.FloorTilt.x)) >= 2)) else if (pointProbe.FloorTilt.y < 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(90.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(270.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(90.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(270.0f))
{ {
@ -1365,7 +1359,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z -= collResult.FloorTilt.y * 2; item->Animation.Velocity.z -= pointProbe.FloorTilt.y * 2;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(180.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(180.0f))
{ {
@ -1388,7 +1382,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(180.0f) // Hit angle = ANGLE(180.0f)
else if (collResult.FloorTilt.y > 0 && ((abs(collResult.FloorTilt.y)) - (abs(collResult.FloorTilt.x)) >= 2)) else if (pointProbe.FloorTilt.y > 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(270.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(90.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(270.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(90.0f))
{ {
@ -1400,7 +1394,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.y * 2; item->Animation.Velocity.z += pointProbe.FloorTilt.y * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(180.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(180.0f))
{ {
@ -1422,7 +1416,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
item->Animation.Velocity.y = 0; item->Animation.Velocity.y = 0;
} }
} }
else if (collResult.FloorTilt.x < 0 && collResult.FloorTilt.y < 0) // Hit angle = 0x2000 else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y < 0) // Hit angle = 0x2000
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(135.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(315.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(135.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(315.0f))
{ {
@ -1434,7 +1428,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += -collResult.FloorTilt.x + -collResult.FloorTilt.y; item->Animation.Velocity.z += -pointProbe.FloorTilt.x + -pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(45.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(225.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(45.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(225.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1456,7 +1450,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(135.0f) // Hit angle = ANGLE(135.0f)
else if (collResult.FloorTilt.x < 0 && collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y > 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(225.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(45.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(225.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(45.0f))
{ {
@ -1468,7 +1462,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += (-collResult.FloorTilt.x) + collResult.FloorTilt.y; item->Animation.Velocity.z += (-pointProbe.FloorTilt.x) + pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(315.0f) && (unsigned short)item->Pose.Orientation.y > ANGLE(135.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(315.0f) && (unsigned short)item->Pose.Orientation.y > ANGLE(135.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1490,7 +1484,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(225.5f) // Hit angle = ANGLE(225.5f)
else if (collResult.FloorTilt.x > 0 && collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y > 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(315.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(135.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(315.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(135.0f))
{ {
@ -1502,7 +1496,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x + collResult.FloorTilt.y; item->Animation.Velocity.z += pointProbe.FloorTilt.x + pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(45.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(225.5f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(45.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(225.5f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1524,7 +1518,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(315.0f) // Hit angle = ANGLE(315.0f)
else if (collResult.FloorTilt.x > 0 && collResult.FloorTilt.y < 0) else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y < 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(45.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(225.5f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(45.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(225.5f))
{ {
@ -1536,7 +1530,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x + (-collResult.FloorTilt.y); item->Animation.Velocity.z += pointProbe.FloorTilt.x + (-pointProbe.FloorTilt.y);
if ((unsigned short)item->Pose.Orientation.y < ANGLE(135.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(315.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(135.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(315.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1558,7 +1552,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Put item back in its last position. // Move item back to its previous position.
item->Pose.Position.x = x; item->Pose.Position.x = x;
item->Pose.Position.y = y; item->Pose.Position.y = y;
item->Pose.Position.z = z; item->Pose.Position.z = z;
@ -1597,7 +1591,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
item->Pose.Position.y = collResult.Position.Floor; item->Pose.Position.y = pointProbe.Position.Floor;
} }
} }
// Check for on top of object. // Check for on top of object.
@ -1605,8 +1599,8 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (yv >= 0) if (yv >= 0)
{ {
prevCollResult = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber); prevPointProbe = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber);
collResult = GetCollision(item); pointProbe = GetCollision(item);
// Bounce off floor. // Bounce off floor.
@ -1614,7 +1608,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
// was always set to 0 by GetHeight() function which was called before the check. // was always set to 0 by GetHeight() function which was called before the check.
// Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21 // Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21
if (item->Pose.Position.y >= prevCollResult.Position.Floor) if (item->Pose.Position.y >= prevPointProbe.Position.Floor)
{ {
// Hit the floor; bounce and slow down. // Hit the floor; bounce and slow down.
if (item->Animation.Velocity.y > 0) if (item->Animation.Velocity.y > 0)
@ -1648,17 +1642,17 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
item->Pose.Position.y = prevCollResult.Position.Floor; item->Pose.Position.y = prevPointProbe.Position.Floor;
} }
} }
// else // else
{ {
// Bounce off ceiling. // Bounce off ceiling.
collResult = GetCollision(item); pointProbe = GetCollision(item);
if (item->Pose.Position.y < collResult.Position.Ceiling) if (item->Pose.Position.y < pointProbe.Position.Ceiling)
{ {
if (y < collResult.Position.Ceiling && if (y < pointProbe.Position.Ceiling &&
(((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) || (((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) ||
((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1))))) ((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1)))))
{ {
@ -1681,13 +1675,13 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
else else
item->Animation.Velocity.z /= 2; item->Animation.Velocity.z /= 2;
// Put item back in its last position. // Move item back to its previous position.
item->Pose.Position.x = x; item->Pose.Position.x = x;
item->Pose.Position.y = y; item->Pose.Position.y = y;
item->Pose.Position.z = z; item->Pose.Position.z = z;
} }
else else
item->Pose.Position.y = collResult.Position.Ceiling; item->Pose.Position.y = pointProbe.Position.Ceiling;
if (item->Animation.Velocity.y < 0) if (item->Animation.Velocity.y < 0)
item->Animation.Velocity.y = -item->Animation.Velocity.y; item->Animation.Velocity.y = -item->Animation.Velocity.y;
@ -1695,14 +1689,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
collResult = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); pointProbe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
if (collResult.RoomNumber != item->RoomNumber) if (pointProbe.RoomNumber != item->RoomNumber)
{ {
if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, collResult.RoomNumber)) if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointProbe.RoomNumber))
Splash(item); Splash(item);
ItemNewRoom(itemNumber, collResult.RoomNumber); ItemNewRoom(itemNumber, pointProbe.RoomNumber);
} }
item->Pose.Position.y -= radius; item->Pose.Position.y -= radius;
@ -1791,8 +1785,8 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
else else
{ {
DoDamage(item, INT_MAX); DoDamage(item, INT_MAX);
DoLotsOfBlood(
DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.x,
laraItem->Pose.Position.y - CLICK(1), laraItem->Pose.Position.y - CLICK(1),
item->Pose.Position.z, item->Pose.Position.z,
laraItem->Animation.Velocity.z, laraItem->Animation.Velocity.z,
@ -1832,10 +1826,6 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
{ {
SoundEffect(GetShatterSound(mesh->staticNumber), (PHD_3DPOS*)mesh); SoundEffect(GetShatterSound(mesh->staticNumber), (PHD_3DPOS*)mesh);
ShatterObject(nullptr, mesh, -128, laraItem->RoomNumber, 0); ShatterObject(nullptr, mesh, -128, laraItem->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = laraItem->RoomNumber;
SmashedMesh[SmashedMeshCount] = mesh;
SmashedMeshCount++;
mesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
} }
else if (coll->Setup.EnableObjectPush) else if (coll->Setup.EnableObjectPush)
ItemPushStatic(laraItem, mesh, coll); ItemPushStatic(laraItem, mesh, coll);
@ -1901,7 +1891,7 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
int rx = (frame->boundingBox.X1 + frame->boundingBox.X2) / 2; int rx = (frame->boundingBox.X1 + frame->boundingBox.X2) / 2;
int rz = (frame->boundingBox.X2 + frame->boundingBox.Z2) / 2; int rz = (frame->boundingBox.X2 + frame->boundingBox.Z2) / 2;
if (frame->boundingBox.Height() > STEP_SIZE) if (frame->boundingBox.Height() > CLICK(1))
{ {
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
@ -1909,7 +1899,7 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
lara->HitDirection = (short)angle; lara->HitDirection = (short)angle;
// TODO: check if a second Lara.hitFrame++; is required there ! // TODO: Check if a second Lara.hitFrame++ is required. -- TokyoSU
lara->HitFrame++; lara->HitFrame++;
if (lara->HitFrame > 30) if (lara->HitFrame > 30)
lara->HitFrame = 30; lara->HitFrame = 30;

View file

@ -2,15 +2,15 @@
#include "Specific/phd_global.h" #include "Specific/phd_global.h"
#include "Specific/trmath.h" #include "Specific/trmath.h"
struct ItemInfo;
struct CollisionInfo;
class FloorInfo; class FloorInfo;
struct CollisionInfo;
struct ItemInfo;
struct MESH_INFO; struct MESH_INFO;
constexpr auto MAX_COLLIDED_OBJECTS = 1024; constexpr auto MAX_COLLIDED_OBJECTS = 1024;
constexpr auto ITEM_RADIUS_YMAX = SECTOR(3); constexpr auto ITEM_RADIUS_YMAX = SECTOR(3);
constexpr auto VEHICLE_COLLISION_TERMINAL_VELOCITY = 30; constexpr auto VEHICLE_COLLISION_TERMINAL_VELOCITY = 30.0f;
extern BOUNDING_BOX GlobalCollisionBounds; extern BOUNDING_BOX GlobalCollisionBounds;
extern ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS]; extern ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS];
@ -36,17 +36,17 @@ bool TestLaraPosition(OBJECT_COLLISION_BOUNDS* bounds, ItemInfo* item, ItemInfo*
bool AlignLaraPosition(Vector3Int* offset, ItemInfo* item, ItemInfo* laraItem); bool AlignLaraPosition(Vector3Int* offset, ItemInfo* item, ItemInfo* laraItem);
bool MoveLaraPosition(Vector3Int* pos, ItemInfo* item, ItemInfo* laraItem); bool MoveLaraPosition(Vector3Int* pos, ItemInfo* item, ItemInfo* laraItem);
bool ItemNearLara(PHD_3DPOS* pos, int radius); bool ItemNearLara(Vector3Int* origin, int radius);
bool ItemNearTarget(PHD_3DPOS* origin, ItemInfo* target, int radius); bool ItemNearTarget(Vector3Int* origin, ItemInfo* targetEntity, int radius);
bool Move3DPosTo3DPos(PHD_3DPOS* origin, PHD_3DPOS* target, int velocity, short angleAdd); bool Move3DPosTo3DPos(PHD_3DPOS* fromPose, PHD_3DPOS* toPose, int velocity, short angleAdd);
bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius); bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius);
bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius); bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius);
bool ItemPushItem(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll, bool spasmEnabled, char bigPush); bool ItemPushItem(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll, bool spasmEnabled, char bigPush);
bool ItemPushStatic(ItemInfo* laraItem, MESH_INFO* mesh, CollisionInfo* coll); bool ItemPushStatic(ItemInfo* laraItem, MESH_INFO* mesh, CollisionInfo* coll);
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, CollisionInfo* coll); bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pose, CollisionInfo* coll);
void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll); void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll);
void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -78,7 +78,7 @@ int TestCollision(ItemInfo* item, ItemInfo* laraItem)
int dz = z1 - z2; int dz = z1 - z2;
int r = r1 + r2; int r = r1 + r2;
if ((pow(dx, 2) + pow(dy, 2) + pow(dz, 2)) < pow(r, 2)) if ((SQUARE(dx) + SQUARE(dy) + SQUARE(dz)) < SQUARE(r))
{ {
item->SetBits(JointBitType::Touch, i); item->SetBits(JointBitType::Touch, i);
laraItem->SetBits(JointBitType::Touch, j); laraItem->SetBits(JointBitType::Touch, j);

View file

@ -92,7 +92,7 @@ void DrawNearbyPathfinding(int boxIndex)
void DropEntityPickups(ItemInfo* item) void DropEntityPickups(ItemInfo* item)
{ {
ItemInfo* pickup = NULL; ItemInfo* pickup = nullptr;
for (short pickupNumber = item->CarriedItem; pickupNumber != NO_ITEM; pickupNumber = pickup->CarriedItem) for (short pickupNumber = item->CarriedItem; pickupNumber != NO_ITEM; pickupNumber = pickup->CarriedItem)
{ {
@ -157,7 +157,7 @@ void CreatureYRot2(PHD_3DPOS* srcPos, short angle, short angleAdd)
bool SameZone(CreatureInfo* creature, ItemInfo* target) bool SameZone(CreatureInfo* creature, ItemInfo* target)
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* item = &g_Level.Items[creature->ItemNumber]; auto* item = &g_Level.Items[creature->ItemNumber];
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
@ -449,12 +449,13 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
short top; short top;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
if (!item->Data)
if (!item->IsCreature())
return false; return false;
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* LOT = &creature->LOT; auto* LOT = &creature->LOT;
int* zone = g_Level.Zones[LOT->Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)LOT->Zone][FlipStatus].data();
int boxHeight; int boxHeight;
if (item->BoxNumber != NO_BOX) if (item->BoxNumber != NO_BOX)
@ -462,7 +463,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
else else
boxHeight = item->Floor; boxHeight = item->Floor;
auto oldPos = item->Pose.Position; auto prevPos = item->Pose.Position;
AnimateItem(item); AnimateItem(item);
@ -477,7 +478,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
int y = item->Pose.Position.y + bounds->Y1; int y = item->Pose.Position.y + bounds->Y1;
short roomNumber = item->RoomNumber; short roomNumber = item->RoomNumber;
GetFloor(oldPos.x, y, oldPos.z, &roomNumber); GetFloor(prevPos.x, y, prevPos.z, &roomNumber);
FloorInfo* floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); FloorInfo* floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber);
// TODO: Check why some blocks have box = -1 assigned to them -- Lwmte, 10.11.21 // TODO: Check why some blocks have box = -1 assigned to them -- Lwmte, 10.11.21
@ -506,18 +507,18 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
{ {
xPos = item->Pose.Position.x / SECTOR(1); xPos = item->Pose.Position.x / SECTOR(1);
zPos = item->Pose.Position.z / SECTOR(1); zPos = item->Pose.Position.z / SECTOR(1);
shiftX = oldPos.x / SECTOR(1); shiftX = prevPos.x / SECTOR(1);
shiftZ = oldPos.z / SECTOR(1); shiftZ = prevPos.z / SECTOR(1);
if (xPos < shiftX) if (xPos < shiftX)
item->Pose.Position.x = oldPos.x & (~(SECTOR(1) - 1)); item->Pose.Position.x = prevPos.x & (~WALL_MASK);
else if (xPos > shiftX) else if (xPos > shiftX)
item->Pose.Position.x = oldPos.x | (SECTOR(1) - 1); item->Pose.Position.x = prevPos.x | WALL_MASK;
if (zPos < shiftZ) if (zPos < shiftZ)
item->Pose.Position.z = oldPos.z & (~(SECTOR(1) - 1)); item->Pose.Position.z = prevPos.z & (~WALL_MASK);
else if (zPos > shiftZ) else if (zPos > shiftZ)
item->Pose.Position.z = oldPos.z | (SECTOR(1) - 1); item->Pose.Position.z = prevPos.z | (WALL_MASK);
floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber);
height = g_Level.Boxes[floor->Box].height; height = g_Level.Boxes[floor->Box].height;
@ -538,8 +539,8 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
int x = item->Pose.Position.x; int x = item->Pose.Position.x;
int z = item->Pose.Position.z; int z = item->Pose.Position.z;
xPos = x & (SECTOR(1) - 1); xPos = x & WALL_MASK;
zPos = z & (SECTOR(1) - 1); zPos = z & WALL_MASK;
short radius = Objects[item->ObjectNumber].radius; short radius = Objects[item->ObjectNumber].radius;
shiftX = 0; shiftX = 0;
shiftZ = 0; shiftZ = 0;
@ -669,8 +670,8 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
{ {
if (item->Pose.Position.y + top < ceiling) if (item->Pose.Position.y + top < ceiling)
{ {
item->Pose.Position.x = oldPos.x; item->Pose.Position.x = prevPos.x;
item->Pose.Position.z = oldPos.z; item->Pose.Position.z = prevPos.z;
dy = LOT->Fly; dy = LOT->Fly;
} }
else else
@ -689,13 +690,13 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
} }
else if (item->Pose.Position.y <= height) else if (item->Pose.Position.y <= height)
{ {
dy = 0;
item->Pose.Position.y = height; item->Pose.Position.y = height;
dy = 0;
} }
else else
{ {
item->Pose.Position.x = oldPos.x; item->Pose.Position.x = prevPos.x;
item->Pose.Position.z = oldPos.z; item->Pose.Position.z = prevPos.z;
dy = -LOT->Fly; dy = -LOT->Fly;
} }
@ -732,7 +733,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
if (item->Pose.Position.y > item->Floor) if (item->Pose.Position.y > item->Floor)
{ {
if (item->Pose.Position.y > (item->Floor + CLICK(1))) if (item->Pose.Position.y > (item->Floor + CLICK(1)))
item->Pose.Position = oldPos; item->Pose.Position = prevPos;
else else
item->Pose.Position.y = item->Floor; item->Pose.Position.y = item->Floor;
} }
@ -749,7 +750,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
top = bounds->Y1; // TODO: check if Y1 or Y2 top = bounds->Y1; // TODO: check if Y1 or Y2
if (item->Pose.Position.y + top < ceiling) if (item->Pose.Position.y + top < ceiling)
item->Pose.Position = oldPos; item->Pose.Position = prevPos;
floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
item->Floor = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); item->Floor = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z);
@ -765,7 +766,6 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
} }
CreatureSwitchRoom(itemNumber); CreatureSwitchRoom(itemNumber);
return true; return true;
} }
@ -884,7 +884,7 @@ int ValidBox(ItemInfo* item, short zoneNumber, short boxNumber)
return false; return false;
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber) if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber)
return false; return false;
@ -970,7 +970,7 @@ int UpdateLOT(LOTInfo* LOT, int depth)
int SearchLOT(LOTInfo* LOT, int depth) int SearchLOT(LOTInfo* LOT, int depth)
{ {
int* zone = g_Level.Zones[LOT->Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)LOT->Zone][FlipStatus].data();
int searchZone = zone[LOT->Head]; int searchZone = zone[LOT->Head];
if (depth <= 0) if (depth <= 0)
@ -1084,7 +1084,7 @@ int CreatureActive(short itemNumber)
if (item->Flags & IFLAG_KILLED) if (item->Flags & IFLAG_KILLED)
return false; // Object is already dead return false; // Object is already dead
if (item->Status == ITEM_INVISIBLE || !item->Data.is<CreatureInfo>()) if (item->Status == ITEM_INVISIBLE || !item->IsCreature())
{ {
if (!EnableEntityAI(itemNumber, 0)) if (!EnableEntityAI(itemNumber, 0))
return false; // AI couldn't be activated return false; // AI couldn't be activated
@ -1162,7 +1162,7 @@ int CreatureVault(short itemNumber, short angle, int vault, int shift)
vault = 0; vault = 0;
else if (item->Floor > y + CHECK_CLICK(7)) else if (item->Floor > y + CHECK_CLICK(7))
vault = -4; vault = -4;
// FIXME: edit assets adding climb down animations for Von Croy and baddys? // FIXME: edit assets adding climb down animations for Von Croy and baddies?
else if (item->Floor > y + CHECK_CLICK(5) && else if (item->Floor > y + CHECK_CLICK(5) &&
item->ObjectNumber != ID_VON_CROY && item->ObjectNumber != ID_VON_CROY &&
item->ObjectNumber != ID_BADDY1 && item->ObjectNumber != ID_BADDY1 &&
@ -1367,7 +1367,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
if (g_Level.AIObjects.size() > 0) if (g_Level.AIObjects.size() > 0)
{ {
AI_OBJECT* foundObject = NULL; AI_OBJECT* foundObject = nullptr;
for (int i = 0; i < g_Level.AIObjects.size(); i++) for (int i = 0; i < g_Level.AIObjects.size(); i++)
{ {
@ -1375,7 +1375,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
if (aiObject->objectNumber == objectNumber && aiObject->triggerFlags == item->ItemFlags[3] && aiObject->roomNumber != NO_ROOM) if (aiObject->objectNumber == objectNumber && aiObject->triggerFlags == item->ItemFlags[3] && aiObject->roomNumber != NO_ROOM)
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box;
@ -1394,7 +1394,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
} }
} }
if (foundObject != NULL) if (foundObject != nullptr)
{ {
auto* aiItem = creature->AITarget; auto* aiItem = creature->AITarget;
@ -1435,7 +1435,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
creature->Enemy = LaraItem; creature->Enemy = LaraItem;
} }
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = NO_BOX; item->BoxNumber = NO_BOX;
@ -1457,8 +1457,8 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
// This prevents enemies from running to Lara and attacking nothing when she is hanging or shimmying. -- Lwmte, 27.06.22 // This prevents enemies from running to Lara and attacking nothing when she is hanging or shimmying. -- Lwmte, 27.06.22
bool reachable = false; bool reachable = false;
if (object->zoneType == ZoneType::ZONE_FLYER || if (object->ZoneType == ZoneType::Flyer ||
(object->zoneType == ZoneType::ZONE_WATER && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber))) (object->ZoneType == ZoneType::Water && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber)))
{ {
reachable = true; // If NPC is flying or swimming in water, always reach Lara reachable = true; // If NPC is flying or swimming in water, always reach Lara
} }

View file

@ -444,9 +444,8 @@ GameStatus DoTitle(int index, std::string const& ambient)
g_Gui.SetMenuToDisplay(Menu::Title); g_Gui.SetMenuToDisplay(Menu::Title);
g_Gui.SetSelectedOption(0); g_Gui.SetSelectedOption(0);
// Initialise ponytails
InitialiseHair(); InitialiseHair();
InitialiseNodeScripts();
InitialiseItemBoxData(); InitialiseItemBoxData();
g_GameScript->OnStart(); g_GameScript->OnStart();
@ -570,9 +569,8 @@ GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
// Initialise flyby cameras // Initialise flyby cameras
InitSpotCamSequences(); InitSpotCamSequences();
// Initialise ponytails
InitialiseHair(); InitialiseHair();
InitialiseNodeScripts();
InitialiseItemBoxData(); InitialiseItemBoxData();
if (loadFromSavegame) if (loadFromSavegame)

View file

@ -121,10 +121,6 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
ShatterImpactData.impactDirection = direction; ShatterImpactData.impactDirection = direction;
ShatterImpactData.impactLocation = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z); ShatterImpactData.impactLocation = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z);
ShatterObject(nullptr, mesh, 128, target2.roomNumber, 0); ShatterObject(nullptr, mesh, 128, target2.roomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = target2.roomNumber;
SmashedMesh[SmashedMeshCount] = mesh;
++SmashedMeshCount;
mesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
SoundEffect(GetShatterSound(mesh->staticNumber), (PHD_3DPOS*)mesh); SoundEffect(GetShatterSound(mesh->staticNumber), (PHD_3DPOS*)mesh);
} }

View file

@ -112,15 +112,15 @@ void DisableEntityAI(short itemNumber)
item->Data = nullptr; item->Data = nullptr;
} }
void InitialiseSlot(short itemNum, short slot, bool makeTarget) void InitialiseSlot(short itemNumber, short slot, bool makeTarget)
{ {
auto* item = &g_Level.Items[itemNum]; auto* item = &g_Level.Items[itemNumber];
auto* obj = &Objects[item->ObjectNumber]; auto* object = &Objects[item->ObjectNumber];
item->Data = CreatureInfo(); item->Data = CreatureInfo();
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
InitialiseLOTarray(itemNum);
creature->ItemNumber = itemNum; InitialiseLOTarray(itemNumber);
creature->ItemNumber = itemNumber;
creature->Mood = MoodType::Bored; creature->Mood = MoodType::Bored;
creature->JointRotation[0] = 0; creature->JointRotation[0] = 0;
creature->JointRotation[1] = 0; creature->JointRotation[1] = 0;
@ -136,12 +136,12 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
creature->MonkeySwingAhead = false; creature->MonkeySwingAhead = false;
creature->LOT.CanJump = false; creature->LOT.CanJump = false;
creature->LOT.CanMonkey = false; creature->LOT.CanMonkey = false;
creature->LOT.IsAmphibious = false; // only the crocodile can go water and land. (default: true) creature->LOT.IsAmphibious = false; // True for crocodile by default as the only the crocodile that can move in water and on land.
creature->LOT.IsJumping = false; creature->LOT.IsJumping = false;
creature->LOT.IsMonkeying = false; creature->LOT.IsMonkeying = false;
creature->MaxTurn = ANGLE(1); creature->MaxTurn = ANGLE(1);
creature->Flags = 0; creature->Flags = 0;
creature->Enemy = NULL; creature->Enemy = nullptr;
creature->LOT.Fly = NO_FLYING; creature->LOT.Fly = NO_FLYING;
creature->LOT.BlockMask = BLOCKED; creature->LOT.BlockMask = BLOCKED;
@ -155,117 +155,116 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
else else
creature->AITarget = nullptr; creature->AITarget = nullptr;
switch (obj->zoneType) switch (object->ZoneType)
{ {
default: default:
case ZONE_NULL: case ZoneType::None:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
obj->zoneType = ZONE_BASIC; // only entity that use CreatureActive() will reach InitialiseSlot() ! object->ZoneType = ZoneType::Basic; // Only entities that use CreatureActive() will reach InitialiseSlot().
break; break;
case ZONE_SKELLY: // Can jump.
// Can jump case ZoneType::Skeleton:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.Zone = ZONE_SKELLY; creature->LOT.Zone = ZoneType::Skeleton;
break; break;
case ZONE_BASIC: case ZoneType::Basic:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
creature->LOT.Zone = ZONE_BASIC; creature->LOT.Zone = ZoneType::Basic;
break; break;
case ZONE_FLYER: // Can fly.
// Can fly case ZoneType::Flyer:
creature->LOT.Step = SECTOR(20); creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20); creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = DEFAULT_FLY_UPDOWN_SPEED; creature->LOT.Fly = DEFAULT_FLY_UPDOWN_SPEED;
creature->LOT.Zone = ZONE_FLYER; creature->LOT.Zone = ZoneType::Flyer;
break; break;
case ZONE_WATER: // Can swim.
// Can swim case ZoneType::Water:
creature->LOT.Step = SECTOR(20); creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20); creature->LOT.Drop = -SECTOR(20);
creature->LOT.Zone = ZONE_WATER; creature->LOT.Zone = ZoneType::Water;
if (item->ObjectNumber == ID_CROCODILE) if (item->ObjectNumber == ID_CROCODILE)
{ {
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED / 2; // is more slow than the other underwater entity creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED / 2; // Slower than the other underwater creatures.
creature->LOT.IsAmphibious = true; // crocodile can walk and swim. creature->LOT.IsAmphibious = true; // Can walk and swim.
} }
else if (item->ObjectNumber == ID_BIG_RAT) else if (item->ObjectNumber == ID_BIG_RAT)
{ {
creature->LOT.Fly = NO_FLYING; // dont want the bigrat to be able to go in water (just the surface !) creature->LOT.Fly = NO_FLYING; // Can't swim underwater, only on the surface.
creature->LOT.IsAmphibious = true; // bigrat can walk and swim. creature->LOT.IsAmphibious = true; // Can walk and swim.
} }
else else
{
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED; creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
}
break; break;
case ZONE_HUMAN_CLASSIC: // Can climb.
// Can climb case ZoneType::HumanClassic:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_JUMP: // Can climb and jump.
// Can climb and jump case ZoneType::HumanJump:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_JUMP_AND_MONKEY: // Can climb, jump, monkeyswing.
// Can climb, jump, monkey case ZoneType::HumanJumpAndMonkey:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.CanMonkey = true; creature->LOT.CanMonkey = true;
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_LONGJUMP_AND_MONKEY: // Can climb, jump, monkey swing, long jump.
// Can climb, jump, monkey, long jump case ZoneType::HumanLongJumpAndMonkey:
creature->LOT.Step = SECTOR(1) + CLICK(3); creature->LOT.Step = SECTOR(1) + CLICK(3);
creature->LOT.Drop = -(SECTOR(1) + CLICK(3)); creature->LOT.Drop = -(SECTOR(1) + CLICK(3));
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.CanMonkey = true; creature->LOT.CanMonkey = true;
creature->LOT.Zone = ZONE_VON_CROY; creature->LOT.Zone = ZoneType::VonCroy;
break; break;
case ZONE_SPIDER: case ZoneType::Spider:
creature->LOT.Step = SECTOR(1) - CLICK(2); creature->LOT.Step = SECTOR(1) - CLICK(2);
creature->LOT.Drop = -(SECTOR(1) - CLICK(2)); creature->LOT.Drop = -(SECTOR(1) - CLICK(2));
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_BLOCKABLE: case ZoneType::Blockable:
creature->LOT.BlockMask = BLOCKABLE; creature->LOT.BlockMask = BLOCKABLE;
creature->LOT.Zone = ZONE_BASIC; creature->LOT.Zone = ZoneType::Basic;
break; break;
case ZONE_APE: case ZoneType::Ape:
creature->LOT.Step = CLICK(2); creature->LOT.Step = CLICK(2);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
break; break;
case ZONE_SOPHIALEE: case ZoneType::SophiaLee:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -CLICK(3); creature->LOT.Drop = -CLICK(3);
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
} }
ClearLOT(&creature->LOT); ClearLOT(&creature->LOT);
if (itemNum != Lara.ItemNumber) if (itemNumber != Lara.ItemNumber)
CreateZone(item); CreateZone(item);
SlotsUsed++; SlotsUsed++;
@ -304,16 +303,16 @@ void ClearLOT(LOTInfo* LOT)
void CreateZone(ItemInfo* item) void CreateZone(ItemInfo* item)
{ {
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* r = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(r, item->Pose.Position.x - r->x, item->Pose.Position.z - r->z)->Box; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box;
if (creature->LOT.Fly) if (creature->LOT.Fly)
{ {
BOX_NODE* node = creature->LOT.Node.data(); auto* node = creature->LOT.Node.data();
creature->LOT.ZoneCount = 0; creature->LOT.ZoneCount = 0;
for (int i = 0; i < g_Level.Boxes.size(); i++) for (size_t i = 0; i < g_Level.Boxes.size(); i++)
{ {
node->boxNumber = i; node->boxNumber = i;
node++; node++;
@ -322,8 +321,8 @@ void CreateZone(ItemInfo* item)
} }
else else
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][0].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][0].data();
int* flippedZone = g_Level.Zones[creature->LOT.Zone][1].data(); int* flippedZone = g_Level.Zones[(int)creature->LOT.Zone][1].data();
int zoneNumber = zone[item->BoxNumber]; int zoneNumber = zone[item->BoxNumber];
int flippedZoneNumber = flippedZone[item->BoxNumber]; int flippedZoneNumber = flippedZone[item->BoxNumber];
@ -331,7 +330,7 @@ void CreateZone(ItemInfo* item)
auto* node = creature->LOT.Node.data(); auto* node = creature->LOT.Node.data();
creature->LOT.ZoneCount = 0; creature->LOT.ZoneCount = 0;
for (int i = 0; i < g_Level.Boxes.size(); i++) for (size_t i = 0; i < g_Level.Boxes.size(); i++)
{ {
if (*zone == zoneNumber || *flippedZone == flippedZoneNumber) if (*zone == zoneNumber || *flippedZone == flippedZoneNumber)
{ {

View file

@ -56,6 +56,7 @@ int TriggerActive(ItemInfo* item)
flag = !flag; flag = !flag;
} }
} }
return flag; return flag;
} }

View file

@ -1,6 +1,7 @@
#include "framework.h" #include "framework.h"
#include "Game/control/volume.h" #include "Game/control/volume.h"
#include <filesystem>
#include "Game/animation.h" #include "Game/animation.h"
#include "Game/items.h" #include "Game/items.h"
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
@ -79,7 +80,7 @@ namespace TEN::Control::Volumes
volume->Status = TriggerStatus::Entering; volume->Status = TriggerStatus::Entering;
if (!set->OnEnter.Function.empty() && set->OnEnter.CallCounter != 0) if (!set->OnEnter.Function.empty() && set->OnEnter.CallCounter != 0)
{ {
g_GameScript->ExecuteFunction(set->OnEnter.Function, triggerer, set->OnEnter.Argument); g_GameScript->ExecuteFunction(set->OnEnter.Function, triggerer, set->OnEnter.Data);
if (set->OnEnter.CallCounter != NO_CALL_COUNTER) if (set->OnEnter.CallCounter != NO_CALL_COUNTER)
set->OnEnter.CallCounter--; set->OnEnter.CallCounter--;
} }
@ -89,7 +90,7 @@ namespace TEN::Control::Volumes
volume->Status = TriggerStatus::Inside; volume->Status = TriggerStatus::Inside;
if (!set->OnInside.Function.empty() && set->OnInside.CallCounter != 0) if (!set->OnInside.Function.empty() && set->OnInside.CallCounter != 0)
{ {
g_GameScript->ExecuteFunction(set->OnInside.Function, triggerer, set->OnInside.Argument); g_GameScript->ExecuteFunction(set->OnInside.Function, triggerer, set->OnInside.Data);
if (set->OnInside.CallCounter != NO_CALL_COUNTER) if (set->OnInside.CallCounter != NO_CALL_COUNTER)
set->OnInside.CallCounter--; set->OnInside.CallCounter--;
} }
@ -108,7 +109,7 @@ namespace TEN::Control::Volumes
volume->Status = TriggerStatus::Leaving; volume->Status = TriggerStatus::Leaving;
if (!set->OnLeave.Function.empty() && set->OnLeave.CallCounter != 0) if (!set->OnLeave.Function.empty() && set->OnLeave.CallCounter != 0)
{ {
g_GameScript->ExecuteFunction(set->OnLeave.Function, triggerer, set->OnLeave.Argument); g_GameScript->ExecuteFunction(set->OnLeave.Function, triggerer, set->OnLeave.Data);
if (set->OnLeave.CallCounter != NO_CALL_COUNTER) if (set->OnLeave.CallCounter != NO_CALL_COUNTER)
set->OnLeave.CallCounter--; set->OnLeave.CallCounter--;
} }
@ -155,4 +156,53 @@ namespace TEN::Control::Volumes
else else
TestVolumes(item->RoomNumber, bbox, TriggerVolumeActivators::Movable, itemNumber); TestVolumes(item->RoomNumber, bbox, TriggerVolumeActivators::Movable, itemNumber);
} }
void InitialiseNodeScripts()
{
static const std::string nodeScriptPath = "Scripts/Engine/NodeCatalogs/";
if (!std::filesystem::exists(nodeScriptPath))
return;
std::vector<std::string> nodeCatalogs;
for (auto& path : std::filesystem::recursive_directory_iterator(nodeScriptPath))
if (path.path().extension() == ".lua")
nodeCatalogs.push_back(path.path().filename().string());
if (nodeCatalogs.size() == 0)
return;
TENLog("Loading node scripts...", LogLevel::Info);
std::sort(nodeCatalogs.rbegin(), nodeCatalogs.rend());
for (auto& file : nodeCatalogs)
g_GameScript->ExecuteScriptFile(nodeScriptPath + file);
TENLog(std::to_string(nodeCatalogs.size()) + " node catalogs were found and loaded.", LogLevel::Info);
int nodeCount = 0;
for (auto& set : g_Level.EventSets)
{
if ((set.OnEnter.Mode == VolumeEventMode::Nodes) && (set.OnEnter.Data.size() > 0))
{
g_GameScript->ExecuteString(set.OnEnter.Data);
nodeCount++;
}
if ((set.OnInside.Mode == VolumeEventMode::Nodes) && (set.OnInside.Data.size() > 0))
{
g_GameScript->ExecuteString(set.OnInside.Data);
nodeCount++;
}
if ((set.OnLeave.Mode == VolumeEventMode::Nodes) && (set.OnLeave.Data.size() > 0))
{
g_GameScript->ExecuteString(set.OnLeave.Data);
nodeCount++;
}
}
if (nodeCount != 0)
TENLog(std::to_string(nodeCount) + " node scripts were found and loaded.", LogLevel::Info);
}
} }

View file

@ -61,4 +61,6 @@ namespace TEN::Control::Volumes
void TestVolumes(short itemNum); void TestVolumes(short itemNum);
void TestVolumes(short roomNumber, MESH_INFO* mesh); void TestVolumes(short roomNumber, MESH_INFO* mesh);
void TestVolumes(CAMERA_INFO* camera); void TestVolumes(CAMERA_INFO* camera);
void InitialiseNodeScripts();
} }

View file

@ -15,14 +15,14 @@ namespace TEN::Control::Volumes
enum class VolumeEventMode enum class VolumeEventMode
{ {
LevelScript, LevelScript,
Constructor Nodes
}; };
struct VolumeEvent struct VolumeEvent
{ {
VolumeEventMode Mode; VolumeEventMode Mode;
std::string Function; std::string Function;
std::string Argument; std::string Data;
int CallCounter; int CallCounter;
}; };

View file

@ -69,11 +69,19 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
if (mesh) if (mesh)
{ {
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
return;
isStatic = true; isStatic = true;
meshIndex = StaticObjects[mesh->staticNumber].meshNumber; meshIndex = StaticObjects[mesh->staticNumber].meshNumber;
yRot = mesh->pos.Orientation.y; yRot = mesh->pos.Orientation.y;
pos = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z); pos = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z);
scale = mesh->scale; scale = mesh->scale;
mesh->flags &= ~StaticMeshFlags::SM_VISIBLE;
SmashedMeshRoom[SmashedMeshCount] = roomNumber;
SmashedMesh[SmashedMeshCount] = mesh;
SmashedMeshCount++;
} }
else else
{ {

View file

@ -1495,7 +1495,7 @@ void GuiController::SetupAmmoSelector()
{ {
num++; num++;
ammo_object_list[0].invitem = INV_OBJECT_PISTOLS_AMMO; ammo_object_list[0].invitem = INV_OBJECT_PISTOLS_AMMO;
ammo_object_list[0].amount = -1; ammo_object_list[0].amount = AmountPistolsAmmo;
num_ammo_slots = num; num_ammo_slots = num;
current_ammo_type = &CurrentPistolsAmmoType; current_ammo_type = &CurrentPistolsAmmoType;
} }

View file

@ -265,7 +265,7 @@ void AddDisplayPickup(GAME_OBJECT_ID objectNumber)
} }
// No free slot found; pickup the object without displaying it. // No free slot found; pickup the object without displaying it.
PickedUpObject(objectNumber, 0); PickedUpObject(objectNumber);
} }
void InitialisePickupDisplay() void InitialisePickupDisplay()

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <vector>
#include "Specific/phd_global.h" #include "Specific/phd_global.h"
using std::vector;
struct ItemInfo; struct ItemInfo;
struct BOX_NODE struct BOX_NODE
@ -12,34 +13,37 @@ struct BOX_NODE
int boxNumber; int boxNumber;
}; };
enum ZoneType : char enum class ZoneType
{ {
ZONE_NULL = -1, // default zone None = -1,
ZONE_SKELLY = 0, Skeleton,
ZONE_BASIC, Basic,
ZONE_FLYER, Flyer,
ZONE_HUMAN_CLASSIC, HumanClassic,
ZONE_VON_CROY, VonCroy,
ZONE_WATER, Water,
ZONE_MAX, Max,
/// custom zone (using zone above for LOT.zone):
ZONE_HUMAN_JUMP_AND_MONKEY, // Custom zones (above zones are used for LOT.zone):
ZONE_HUMAN_JUMP, HumanJumpAndMonkey,
ZONE_SPIDER, HumanJump,
ZONE_BLOCKABLE, // for trex, shiva, etc.. Spider,
ZONE_SOPHIALEE, // dont want sophia to go down again ! Blockable, // For large creatures such as trex and shiva.
ZONE_APE, // only 2 click climb SophiaLee, // Prevents Sophia from going to lower levels again.
ZONE_HUMAN_LONGJUMP_AND_MONKEY, Ape, // Only 0.5 block climb.
HumanLongJumpAndMonkey,
}; };
struct LOTInfo struct LOTInfo
{ {
bool Initialised; bool Initialised;
std::vector<BOX_NODE> Node; vector<BOX_NODE> Node;
int Head; int Head;
int Tail; int Tail;
ZoneType Zone = ZoneType::None;
Vector3Int Target = Vector3Int::Zero;
int SearchNumber; int SearchNumber;
int BlockMask; int BlockMask;
short Step; short Step;
@ -49,18 +53,16 @@ struct LOTInfo
int RequiredBox; int RequiredBox;
short Fly; short Fly;
bool CanJump; bool CanJump = false;
bool CanMonkey; bool CanMonkey = false;
bool IsJumping; bool IsJumping = false;
bool IsMonkeying; bool IsMonkeying = false;
bool IsAmphibious; bool IsAmphibious = false;
Vector3Int Target;
ZoneType Zone;
}; };
enum class MoodType enum class MoodType
{ {
None,
Bored, Bored,
Attack, Attack,
Escape, Escape,
@ -70,45 +72,43 @@ enum class MoodType
enum class CreatureAIPriority enum class CreatureAIPriority
{ {
None, None,
High, Low,
Medium, Medium,
Low High
}; };
struct CreatureInfo struct CreatureInfo
{ {
short ItemNumber; short ItemNumber = -1;
short MaxTurn; LOTInfo LOT = {};
short JointRotation[4]; MoodType Mood = MoodType::None;
bool HeadLeft; ItemInfo* Enemy = nullptr;
bool HeadRight; ItemInfo* AITarget = nullptr;
short AITargetNumber = -1;
Vector3Int Target = Vector3Int::Zero;
bool Patrol; // Unused? short MaxTurn = 0;
bool Alerted; short JointRotation[4] = {};
bool Friendly; bool HeadLeft = false;
bool HurtByLara; bool HeadRight = false;
bool Poisoned;
bool JumpAhead;
bool MonkeySwingAhead;
bool ReachedGoal;
bool Patrol = false; // Unused?
bool Alerted = false;
bool Friendly = false;
bool HurtByLara = false;
bool Poisoned = false;
bool JumpAhead = false;
bool MonkeySwingAhead = false;
bool ReachedGoal = false;
short FiredWeapon;
short Tosspad; short Tosspad;
short LocationAI; short LocationAI;
short FiredWeapon; short Flags = 0;
LOTInfo LOT;
MoodType Mood;
ItemInfo* Enemy;
short AITargetNumber;
ItemInfo* AITarget;
short Pad; // Unused?
Vector3Int Target;
#ifdef CREATURE_AI_PRIORITY_OPTIMIZATION #ifdef CREATURE_AI_PRIORITY_OPTIMIZATION
CreatureAIPriority Priority; CreatureAIPriority Priority = CreatureAIPriority::None;
size_t FramesSinceLOTUpdate; size_t FramesSinceLOTUpdate = 0;
#endif #endif
short Flags;
}; };

View file

@ -26,7 +26,7 @@ template<class... Ts> struct visitor : Ts... { using Ts::operator()...; };
template<class... Ts> visitor(Ts...)->visitor<Ts...>; // line not needed in C++20... template<class... Ts> visitor(Ts...)->visitor<Ts...>; // line not needed in C++20...
using namespace TEN::Entities::TR4; using namespace TEN::Entities::TR4;
using namespace TEN::Entities::TR5; using namespace TEN::Entities::Creatures::TR5;
using namespace TEN::Entities::Vehicles; using namespace TEN::Entities::Vehicles;
struct ItemInfo; struct ItemInfo;

View file

@ -570,14 +570,17 @@ short CreateItem()
void InitialiseItemArray(int totalItem) void InitialiseItemArray(int totalItem)
{ {
auto* item = &g_Level.Items[g_Level.NumItems]; g_Level.Items.clear();
g_Level.Items.resize(totalItem);
NextItemActive = NO_ITEM; for (int i = 0; i < totalItem; i++)
NextItemFree = g_Level.NumItems; g_Level.Items[i].Index = i;
auto* item = &g_Level.Items[g_Level.NumItems];
if (g_Level.NumItems + 1 < totalItem) if (g_Level.NumItems + 1 < totalItem)
{ {
for(int i = g_Level.NumItems + 1; i < totalItem; i++, item++) for (int i = g_Level.NumItems + 1; i < totalItem; i++, item++)
{ {
item->NextItem = i; item->NextItem = i;
item->Active = false; item->Active = false;
@ -586,6 +589,8 @@ void InitialiseItemArray(int totalItem)
} }
item->NextItem = NO_ITEM; item->NextItem = NO_ITEM;
NextItemActive = NO_ITEM;
NextItemFree = g_Level.NumItems;
} }
short SpawnItem(ItemInfo* item, GAME_OBJECT_ID objectNumber) short SpawnItem(ItemInfo* item, GAME_OBJECT_ID objectNumber)
@ -629,7 +634,6 @@ int GlobalItemReplace(short search, GAME_OBJECT_ID replace)
} }
// Offset values may be used to account for the quirk of room traversal only being able to occur at portals. // Offset values may be used to account for the quirk of room traversal only being able to occur at portals.
// Note: may not work for dynamic items because of FindItem.
void UpdateItemRoom(ItemInfo* item, int height, int xOffset, int zOffset) void UpdateItemRoom(ItemInfo* item, int height, int xOffset, int zOffset)
{ {
float sinY = phd_sin(item->Pose.Orientation.y); float sinY = phd_sin(item->Pose.Orientation.y);
@ -643,7 +647,7 @@ void UpdateItemRoom(ItemInfo* item, int height, int xOffset, int zOffset)
item->Floor = GetFloorHeight(item->Location, x, z).value_or(NO_HEIGHT); item->Floor = GetFloorHeight(item->Location, x, z).value_or(NO_HEIGHT);
if (item->RoomNumber != item->Location.roomNumber) if (item->RoomNumber != item->Location.roomNumber)
ItemNewRoom(FindItem(item), item->Location.roomNumber); ItemNewRoom(item->Index, item->Location.roomNumber);
} }
std::vector<int> FindAllItems(short objectNumber) std::vector<int> FindAllItems(short objectNumber)

View file

@ -70,8 +70,11 @@ struct EntityAnimationData
struct ItemInfo struct ItemInfo
{ {
GAME_OBJECT_ID ObjectNumber; GAME_OBJECT_ID ObjectNumber;
int Status; // ItemStatus enum. int Status; // ItemStatus enum.
bool Active; bool Active;
short Index;
short NextItem; short NextItem;
short NextActive; short NextActive;

View file

@ -57,7 +57,7 @@ void ControlMissile(short fxNumber)
fx->pos.Position.x += velocity * phd_sin(fx->pos.Orientation.y); fx->pos.Position.x += velocity * phd_sin(fx->pos.Orientation.y);
auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber); auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
auto hitLara = ItemNearLara(&fx->pos, 200); auto hitLara = ItemNearLara(&fx->pos.Position, 200);
// Check for hitting something. // Check for hitting something.
if (fx->pos.Position.y >= probe.Position.Floor || if (fx->pos.Position.y >= probe.Position.Floor ||

View file

@ -17,45 +17,45 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, in
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* enemy = creature->Enemy; auto* enemy = creature->Enemy;
bool hit = false; bool hasHit = false;
bool targetable = false; bool isTargetable = false;
if (AI->distance <= pow(MAX_VISIBILITY_DISTANCE, 2) && Targetable(item, AI)) if (AI->distance <= SQUARE(MAX_VISIBILITY_DISTANCE) && Targetable(item, AI))
{ {
int distance = phd_sin(AI->enemyFacing) * enemy->Animation.Velocity.z * pow(MAX_VISIBILITY_DISTANCE, 2) / 300; int distance = phd_sin(AI->enemyFacing) * enemy->Animation.Velocity.z * pow(MAX_VISIBILITY_DISTANCE, 2) / 300;
distance = pow(distance, 2) + AI->distance; distance = SQUARE(distance) + AI->distance;
if (distance <= pow(MAX_VISIBILITY_DISTANCE, 2)) if (distance <= SQUARE(MAX_VISIBILITY_DISTANCE))
{ {
int random = (pow(MAX_VISIBILITY_DISTANCE, 2) - AI->distance) / (pow(MAX_VISIBILITY_DISTANCE, 2) / 0x5000) + 8192; int random = (SQUARE(MAX_VISIBILITY_DISTANCE) - AI->distance) / (SQUARE(MAX_VISIBILITY_DISTANCE) / 0x5000) + 8192;
hit = GetRandomControl() < random; hasHit = GetRandomControl() < random;
} }
else else
hit = false; hasHit = false;
targetable = true; isTargetable = true;
} }
else else
{ {
hit = false; hasHit = false;
targetable = false; isTargetable = false;
} }
if (damage) if (damage)
{ {
if (enemy->IsLara()) if (enemy->IsLara())
{ {
if (hit) if (hasHit)
{ {
DoDamage(enemy, damage); DoDamage(enemy, damage);
CreatureEffect(item, gun, &GunHit); CreatureEffect(item, gun, &GunHit);
} }
else if (targetable) else if (isTargetable)
CreatureEffect(item, gun, &GunMiss); CreatureEffect(item, gun, &GunMiss);
} }
else else
{ {
CreatureEffect(item, gun, &GunShot); CreatureEffect(item, gun, &GunShot);
if (hit) if (hasHit)
{ {
enemy->HitStatus = true; enemy->HitStatus = true;
enemy->HitPoints += damage / -10; enemy->HitPoints += damage / -10;
@ -74,7 +74,7 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, in
// TODO: smash objects // TODO: smash objects
return targetable; return isTargetable;
} }
short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber) short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber)
@ -145,32 +145,32 @@ bool TargetVisible(ItemInfo* item, AI_INFO* AI, float maxAngle)
return false; return false;
// Check just in case. // Check just in case.
auto* creatureInfo = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
if (creatureInfo == nullptr) if (creature == nullptr)
return false; return false;
auto* enemy = creatureInfo->Enemy; auto* enemy = creature->Enemy;
if (enemy == nullptr || enemy->HitPoints == 0) if (enemy == nullptr || enemy->HitPoints == 0)
return false; return false;
short angle = AI->angle - creatureInfo->JointRotation[2]; short angle = AI->angle - creature->JointRotation[2];
if (angle > -ANGLE(maxAngle) && angle < ANGLE(maxAngle)) if (angle > ANGLE(-maxAngle) && angle < ANGLE(maxAngle))
{ {
GameVector start;
GameVector target;
auto& bounds = GetBestFrame(enemy)->boundingBox; auto& bounds = GetBestFrame(enemy)->boundingBox;
start.x = item->Pose.Position.x; auto origin = GameVector(
start.y = item->Pose.Position.y - CLICK(3); item->Pose.Position.x,
start.z = item->Pose.Position.z; item->Pose.Position.y - CLICK(3),
start.roomNumber = item->RoomNumber; item->Pose.Position.z,
item->RoomNumber
target.x = enemy->Pose.Position.x; );
target.y = enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4); auto target = GameVector(
target.z = enemy->Pose.Position.z; enemy->Pose.Position.x,
target.roomNumber = enemy->RoomNumber; // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022 enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4),
enemy->Pose.Position.z,
return LOS(&start, &target); enemy->RoomNumber // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022
);
return LOS(&origin, &target);
} }
return false; return false;

View file

@ -1,6 +1,7 @@
#include "framework.h" #include "framework.h"
#include "Game/pickup/pickup.h" #include "Game/pickup/pickup.h"
#include "pickuputil.h"
#include "Game/animation.h" #include "Game/animation.h"
#include "Game/camera.h" #include "Game/camera.h"
#include "Game/collision/collide_item.h" #include "Game/collision/collide_item.h"
@ -127,10 +128,23 @@ short RPickups[16];
short getThisItemPlease = NO_ITEM; short getThisItemPlease = NO_ITEM;
Vector3Int OldPickupPos; Vector3Int OldPickupPos;
void PickedUpObject(GAME_OBJECT_ID objectID, int count) bool SetInventoryCount(GAME_OBJECT_ID objectID, int count)
{
if (!TryModifyWeapon(Lara, objectID, count, ModificationType::Set) &&
!TryModifyingAmmo(Lara, objectID, count, ModificationType::Set) &&
!TryModifyingKeyItem(Lara, objectID, count, ModificationType::Set) &&
!TryModifyingConsumable(Lara, objectID, count, ModificationType::Set) &&
!TryModifyMiscCount(Lara, objectID, count, ModificationType::Set))
{
return false;
}
return true;
}
void PickedUpObject(GAME_OBJECT_ID objectID, std::optional<int> count)
{ {
// See if the items fit into one of these easy groups. // See if the items fit into one of these easy groups.
if (!TryAddingWeapon(Lara, objectID, count) && if (!TryAddingWeapon(Lara, objectID) &&
!TryAddingAmmo(Lara, objectID, count) && !TryAddingAmmo(Lara, objectID, count) &&
!TryAddingKeyItem(Lara, objectID, count) && !TryAddingKeyItem(Lara, objectID, count) &&
!TryAddingConsumable(Lara, objectID, count) && !TryAddingConsumable(Lara, objectID, count) &&
@ -165,10 +179,10 @@ int GetInventoryCount(GAME_OBJECT_ID objectID)
return 0; return 0;
} }
void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, int count) void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, std::optional<int> count)
{ {
// See if the items fit into one of these easy groups. // See if the items fit into one of these easy groups.
if (!TryRemovingWeapon(Lara, objectID, count) && if (!TryRemovingWeapon(Lara, objectID) &&
!TryRemovingAmmo(Lara, objectID, count) && !TryRemovingAmmo(Lara, objectID, count) &&
!TryRemovingKeyItem(Lara, objectID, count) && !TryRemovingKeyItem(Lara, objectID, count) &&
!TryRemovingConsumable(Lara, objectID, count) && !TryRemovingConsumable(Lara, objectID, count) &&

View file

@ -11,8 +11,9 @@ extern short RPickups[16];
extern Vector3Int OldPickupPos; extern Vector3Int OldPickupPos;
void InitialisePickup(short itemNumber); void InitialisePickup(short itemNumber);
void PickedUpObject(GAME_OBJECT_ID objectID, int count); bool SetInventoryCount(GAME_OBJECT_ID objectID, int count);
void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, int count); void PickedUpObject(GAME_OBJECT_ID objectID, std::optional<int> count = std::nullopt);
void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, std::optional<int> count = std::nullopt);
int GetInventoryCount(GAME_OBJECT_ID objectID); int GetInventoryCount(GAME_OBJECT_ID objectID);
void CollectCarriedItems(ItemInfo* item); void CollectCarriedItems(ItemInfo* item);
void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -20,8 +20,8 @@ static constexpr std::array<AmmoPickupInfo, 14> kAmmo
{ {
{ ID_PISTOLS_AMMO_ITEM, LaraWeaponType::Pistol, WeaponAmmoType::Ammo1, 0 }, { ID_PISTOLS_AMMO_ITEM, LaraWeaponType::Pistol, WeaponAmmoType::Ammo1, 0 },
{ ID_UZI_AMMO_ITEM, LaraWeaponType::Uzi, WeaponAmmoType::Ammo1, 30 }, { ID_UZI_AMMO_ITEM, LaraWeaponType::Uzi, WeaponAmmoType::Ammo1, 30 },
{ ID_SHOTGUN_AMMO1_ITEM, LaraWeaponType::Shotgun, WeaponAmmoType::Ammo1, 36 }, { ID_SHOTGUN_AMMO1_ITEM, LaraWeaponType::Shotgun, WeaponAmmoType::Ammo1, 6 },
{ ID_SHOTGUN_AMMO2_ITEM, LaraWeaponType::Shotgun, WeaponAmmoType::Ammo2, 36 }, { ID_SHOTGUN_AMMO2_ITEM, LaraWeaponType::Shotgun, WeaponAmmoType::Ammo2, 6 },
{ ID_CROSSBOW_AMMO1_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo1, 10 }, { ID_CROSSBOW_AMMO1_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo1, 10 },
{ ID_CROSSBOW_AMMO2_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo2, 10 }, { ID_CROSSBOW_AMMO2_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo2, 10 },
{ ID_CROSSBOW_AMMO3_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo3, 10 }, { ID_CROSSBOW_AMMO3_ITEM, LaraWeaponType::Crossbow, WeaponAmmoType::Ammo3, 10 },
@ -35,7 +35,7 @@ static constexpr std::array<AmmoPickupInfo, 14> kAmmo
} }
}; };
static bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount, bool add) bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType)
{ {
int arrayPos = GetArraySlot(kAmmo, objectID); int arrayPos = GetArraySlot(kAmmo, objectID);
if (-1 == arrayPos) if (-1 == arrayPos)
@ -43,26 +43,40 @@ static bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount
AmmoPickupInfo info = kAmmo[arrayPos]; AmmoPickupInfo info = kAmmo[arrayPos];
auto currentAmmo = lara.Weapons[(int)info.LaraWeaponType].Ammo[(int)info.AmmoType]; auto & currentWeapon = lara.Weapons[(int)info.LaraWeaponType];
if (!currentAmmo.hasInfinite()) auto & currentAmmo = currentWeapon.Ammo[(int)info.AmmoType];
switch(modType)
{ {
int defaultModify = add ? info.Amount : -info.Amount; case ModificationType::Set:
int newVal = int{ currentAmmo.getCount() } + (amount ? amount : defaultModify); currentAmmo = amount.value();
lara.Weapons[(int)info.LaraWeaponType].Ammo[(int)info.AmmoType] = std::max(0, newVal); currentAmmo.setInfinite(amount == -1);
} break;
default:
if (!currentAmmo.hasInfinite())
{
int defaultModify = modType == ModificationType::Add ? info.Amount : -info.Amount;
int newVal = int{ currentAmmo.getCount() } + (amount.has_value() ? amount.value() : defaultModify);
currentAmmo = std::max(0, newVal);
}
break;
};
return true; return true;
} }
// We need the extra bool because amount might be zero to signify the default amount. bool TryAddingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
bool TryAddingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount)
{ {
return TryModifyingAmmo(lara, objectID, amount, true); return TryModifyingAmmo(lara, objectID, amount, ModificationType::Add);
} }
bool TryRemovingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount) bool TryRemovingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
{ {
return TryModifyingAmmo(lara, objectID, -amount, false); if (amount.has_value())
return TryModifyingAmmo(lara, objectID, -amount.value(), ModificationType::Remove);
else
return TryModifyingAmmo(lara, objectID, amount, ModificationType::Remove);
} }
std::optional<int> GetAmmoCount(LaraInfo& lara, GAME_OBJECT_ID objectID) std::optional<int> GetAmmoCount(LaraInfo& lara, GAME_OBJECT_ID objectID)

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
enum class ModificationType;
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
struct LaraInfo; struct LaraInfo;
bool TryAddingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, int amount = 0); bool TryAddingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, std::optional<int> amount = std::nullopt);
bool TryRemovingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, int amount = 0); bool TryRemovingAmmo(LaraInfo&, GAME_OBJECT_ID objectID, std::optional<int> amount = std::nullopt);
bool TryModifyingAmmo(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType);
std::optional<int> GetAmmoCount(LaraInfo&, GAME_OBJECT_ID objectID); std::optional<int> GetAmmoCount(LaraInfo&, GAME_OBJECT_ID objectID);

View file

@ -26,32 +26,44 @@ static constexpr std::array<ConsumablePickupInfo, 3> kConsumables =
} }
}; };
static bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount, bool add) bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType)
{ {
int arrayPos = GetArraySlot(kConsumables, objectID); int arrayPos = GetArraySlot(kConsumables, objectID);
if (-1 == arrayPos) if (-1 == arrayPos)
return false; return false;
ConsumablePickupInfo info = kConsumables[arrayPos]; ConsumablePickupInfo info = kConsumables[arrayPos];
auto & currentAmt = lara.Inventory.*(info.Count);
if (lara.Inventory.*(info.Count) != -1) switch (modType)
{ {
int defaultModify = add ? info.Amount : -info.Amount; case ModificationType::Set:
int newVal = lara.Inventory.*(info.Count) + (amount ? amount : defaultModify); currentAmt = amount.value();
lara.Inventory.*(info.Count) = std::max(0, newVal); break;
default:
if (currentAmt != -1)
{
int defaultModify = ModificationType::Add == modType ? info.Amount : -info.Amount;
int newVal = currentAmt + (amount.has_value() ? amount.value() : defaultModify);
currentAmt = std::max(0, newVal);
}
break;
} }
return true; return true;
} }
bool TryAddingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount) bool TryAddingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
{ {
return TryModifyingConsumable(lara, objectID, amount, true); return TryModifyingConsumable(lara, objectID, amount, ModificationType::Add);
} }
bool TryRemovingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount) bool TryRemovingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
{ {
return TryModifyingConsumable(lara, objectID, -amount, false); if (amount.has_value())
return TryModifyingConsumable(lara, objectID, -amount.value(), ModificationType::Remove);
else
return TryModifyingConsumable(lara, objectID, amount, ModificationType::Remove);
} }
std::optional<int> GetConsumableCount(LaraInfo& lara, GAME_OBJECT_ID objectID) std::optional<int> GetConsumableCount(LaraInfo& lara, GAME_OBJECT_ID objectID)

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
enum class ModificationType;
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
struct LaraInfo; struct LaraInfo;
bool TryAddingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, int amount = 0); bool TryAddingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, std::optional<int> amount = 0);
bool TryRemovingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, int amount = 0); bool TryRemovingConsumable(LaraInfo&, GAME_OBJECT_ID objectID, std::optional<int> amount = 0);
bool TryModifyingConsumable(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType);
std::optional<int> GetConsumableCount(LaraInfo&, GAME_OBJECT_ID objectID); std::optional<int> GetConsumableCount(LaraInfo&, GAME_OBJECT_ID objectID);

View file

@ -2,7 +2,7 @@
#include "Game/pickup/pickup_key_items.h" #include "Game/pickup/pickup_key_items.h"
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
#include "Game/pickup/pickup_misc_items.h" #include "Game/pickup/pickuputil.h"
#include "Objects/objectslist.h" #include "Objects/objectslist.h"
template <size_t N> struct KeyPickupInfo template <size_t N> struct KeyPickupInfo
@ -64,29 +64,42 @@ template<> static std::pair<int*, size_t> GetArrayInternal<0>(LaraInfo& lara, GA
return TestAgainstRange<0>(lara, objectID); return TestAgainstRange<0>(lara, objectID);
} }
static bool TryModifyingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount, bool add) bool TryModifyingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType)
{ {
// kick off the recursion starting at the last element // kick off the recursion starting at the last element
auto result = GetArrayInternal<nInfos - 1>(lara, objectID); auto result = GetArrayInternal<nInfos - 1>(lara, objectID);
if (result.first) if (result.first)
{ {
int defaultModify = add ? kDefaultPickupAmount : -kDefaultPickupAmount; auto& amt = result.first[result.second];
int newVal = int{result.first[result.second]} + (amount ? amount : defaultModify); switch (modType)
result.first[result.second] = std::max(0, newVal); {
case ModificationType::Set:
// infinite key items not yet implemented
amt = amount.value();
break;
default:
int defaultModify = modType == ModificationType::Add ? kDefaultPickupAmount : -kDefaultPickupAmount;
int newVal = int{ result.first[result.second] } + (amount.has_value() ? amount.value() : defaultModify);
result.first[result.second] = std::max(0, newVal);
}
return true; return true;
} }
return false; return false;
} }
bool TryAddingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, int count) bool TryAddingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
{ {
return TryModifyingKeyItem(lara, objectID, count, true); return TryModifyingKeyItem(lara, objectID, amount, ModificationType::Add);
} }
bool TryRemovingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, int count) bool TryRemovingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount)
{ {
return TryModifyingKeyItem(lara, objectID, -count, false); if(amount.has_value())
return TryModifyingKeyItem(lara, objectID, -amount.value(), ModificationType::Remove);
else
return TryModifyingKeyItem(lara, objectID, amount, ModificationType::Remove);
} }
std::optional<int> GetKeyItemCount(LaraInfo& lara, GAME_OBJECT_ID objectID) std::optional<int> GetKeyItemCount(LaraInfo& lara, GAME_OBJECT_ID objectID)

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
enum class ModificationType;
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
struct LaraInfo; struct LaraInfo;
bool TryAddingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, int count); bool TryAddingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount = std::nullopt);
bool TryRemovingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, int count); bool TryRemovingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount = std::nullopt);
bool TryModifyingKeyItem(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType);
std::optional<int> GetKeyItemCount(LaraInfo& lara, GAME_OBJECT_ID objectID); std::optional<int> GetKeyItemCount(LaraInfo& lara, GAME_OBJECT_ID objectID);

View file

@ -1,8 +1,6 @@
#include "framework.h" #include "framework.h"
#include "Game/pickup/pickup_misc_items.h" #include "Game/pickup/pickup_misc_items.h"
#include <array>
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
#include "Game/pickup/pickuputil.h" #include "Game/pickup/pickuputil.h"
#include "Objects/objectslist.h" #include "Objects/objectslist.h"
@ -34,11 +32,14 @@ auto LaserSightIsEquipped(LaraInfo& lara)
return false; return false;
}; };
static bool TryModifyMiscCount(LaraInfo& lara, GAME_OBJECT_ID objectID, bool add) bool TryModifyMiscCount(LaraInfo & lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType)
{ {
// If adding, replace the small/large waterskin with one of the requested // If adding, replace the small/large waterskin with one of the requested
// capacity. If removing, only remove the waterskin if it contains the given // capacity. If removing, only remove the waterskin if it contains the given
// capacity. // capacity.
bool add = ModificationType::Add == modType || ((ModificationType::Set == modType) && amount != 0);
auto modifyWaterSkinAmount = [&](byte& currentFlag, byte newFlag) auto modifyWaterSkinAmount = [&](byte& currentFlag, byte newFlag)
{ {
if (add) if (add)
@ -141,12 +142,12 @@ static bool TryModifyMiscCount(LaraInfo& lara, GAME_OBJECT_ID objectID, bool add
bool TryAddMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID) bool TryAddMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID)
{ {
return TryModifyMiscCount(lara, objectID, true); return TryModifyMiscCount(lara, objectID, std::nullopt, ModificationType::Add);
} }
bool TryRemoveMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID) bool TryRemoveMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID)
{ {
return TryModifyMiscCount(lara, objectID, false); return TryModifyMiscCount(lara, objectID, std::nullopt, ModificationType::Remove);
} }
std::optional<bool> HasMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID) std::optional<bool> HasMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID)

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
enum class ModificationType;
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
struct LaraInfo; struct LaraInfo;
bool TryAddMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID); bool TryAddMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID);
bool TryRemoveMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID); bool TryRemoveMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID);
bool TryModifyMiscCount(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> amount, ModificationType modType);
std::optional<bool> HasMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID); std::optional<bool> HasMiscItem(LaraInfo& lara, GAME_OBJECT_ID objectID);

View file

@ -3,30 +3,38 @@
#include <array> #include <array>
#include "lara_fire.h"
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
#include "Game/pickup/pickuputil.h" #include "Game/pickup/pickuputil.h"
#include "Game/pickup/pickup_ammo.h" #include "Game/pickup/pickup_ammo.h"
#include "Objects/objectslist.h" #include "Objects/objectslist.h"
enum class HolsterType
{
Hips,
Back
};
struct WeaponPickupInfo struct WeaponPickupInfo
{ {
GAME_OBJECT_ID ObjectID; GAME_OBJECT_ID ObjectID;
GAME_OBJECT_ID AmmoID; // When the player picks up a weapon, they get one clip's worth of this ammo. GAME_OBJECT_ID AmmoID; // When the player picks up a weapon, they get one clip's worth of this ammo.
LaraWeaponType LaraWeaponType; LaraWeaponType LaraWeaponType;
HolsterType Holster;
}; };
static constexpr std::array<WeaponPickupInfo, 9> kWeapons static constexpr std::array<WeaponPickupInfo, 9> kWeapons
{ {
{ {
{ ID_PISTOLS_ITEM, ID_PISTOLS_AMMO_ITEM, LaraWeaponType::Pistol }, { ID_PISTOLS_ITEM, ID_PISTOLS_AMMO_ITEM, LaraWeaponType::Pistol, HolsterType::Hips },
{ ID_UZI_ITEM, ID_UZI_AMMO_ITEM, LaraWeaponType::Uzi }, { ID_UZI_ITEM, ID_UZI_AMMO_ITEM, LaraWeaponType::Uzi, HolsterType::Hips},
{ ID_SHOTGUN_ITEM, ID_SHOTGUN_AMMO1_ITEM, LaraWeaponType::Shotgun }, { ID_SHOTGUN_ITEM, ID_SHOTGUN_AMMO1_ITEM, LaraWeaponType::Shotgun, HolsterType::Back },
{ ID_CROSSBOW_ITEM, ID_CROSSBOW_AMMO1_ITEM, LaraWeaponType::Crossbow }, { ID_CROSSBOW_ITEM, ID_CROSSBOW_AMMO1_ITEM, LaraWeaponType::Crossbow, HolsterType::Back},
{ ID_REVOLVER_ITEM, ID_REVOLVER_AMMO_ITEM, LaraWeaponType::Revolver }, { ID_REVOLVER_ITEM, ID_REVOLVER_AMMO_ITEM, LaraWeaponType::Revolver, HolsterType::Hips},
{ ID_HK_ITEM, ID_HK_AMMO_ITEM, LaraWeaponType::HK }, { ID_HK_ITEM, ID_HK_AMMO_ITEM, LaraWeaponType::HK, HolsterType::Back},
{ ID_GRENADE_GUN_ITEM, ID_GRENADE_AMMO1_ITEM, LaraWeaponType::GrenadeLauncher }, { ID_GRENADE_GUN_ITEM, ID_GRENADE_AMMO1_ITEM, LaraWeaponType::GrenadeLauncher, HolsterType::Back },
{ ID_ROCKET_LAUNCHER_ITEM, ID_ROCKET_LAUNCHER_AMMO_ITEM, LaraWeaponType::RocketLauncher }, { ID_ROCKET_LAUNCHER_ITEM, ID_ROCKET_LAUNCHER_AMMO_ITEM, LaraWeaponType::RocketLauncher, HolsterType::Back },
{ ID_HARPOON_ITEM, ID_HARPOON_AMMO_ITEM, LaraWeaponType::HarpoonGun } { ID_HARPOON_ITEM, ID_HARPOON_AMMO_ITEM, LaraWeaponType::HarpoonGun, HolsterType::Back }
} }
}; };
@ -41,7 +49,7 @@ static int GetWeapon(GAME_OBJECT_ID objectID)
return -1; return -1;
} }
static bool TryModifyWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int ammoAmount, bool add) bool TryModifyWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> count, ModificationType type)
{ {
int arrayPos = GetArraySlot(kWeapons, objectID); int arrayPos = GetArraySlot(kWeapons, objectID);
if (-1 == arrayPos) if (-1 == arrayPos)
@ -51,25 +59,56 @@ static bool TryModifyWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int ammoAmo
// Set the SelectedAmmo type to WeaponAmmoType::Ammo1 (0) if adding the weapon for the first time. // Set the SelectedAmmo type to WeaponAmmoType::Ammo1 (0) if adding the weapon for the first time.
// Note that this refers to the index of the weapon's ammo array, and not the weapon's actual ammunition count. // Note that this refers to the index of the weapon's ammo array, and not the weapon's actual ammunition count.
if (!lara.Weapons[(int)info.LaraWeaponType].Present) auto& currWeapon = lara.Weapons[(int)info.LaraWeaponType];
lara.Weapons[(int)info.LaraWeaponType].SelectedAmmo = WeaponAmmoType::Ammo1;
if (!currWeapon.Present)
lara.Weapons[(int)info.LaraWeaponType].Present = add; currWeapon.SelectedAmmo = WeaponAmmoType::Ammo1;
auto ammoID = info.AmmoID;
return add ? TryAddingAmmo(lara, ammoID, ammoAmount) : TryRemovingAmmo(lara, ammoID, ammoAmount); bool add = ModificationType::Add == type || ((ModificationType::Set == type) && count != 0);
currWeapon.Present = add;
if(!add)
{
if (info.LaraWeaponType == lara.Control.Weapon.GunType || info.LaraWeaponType == lara.Control.Weapon.LastGunType)
{
lara.Control.Weapon.RequestGunType = LaraWeaponType::None;
// If Lara has pistols and it's not the pistols we're removing, set them
// as the "next weapon" so that Lara equips them next.
if (LaraWeaponType::Pistol == info.LaraWeaponType || !lara.Weapons[(int)LaraWeaponType::Pistol].Present)
{
lara.Control.Weapon.LastGunType = LaraWeaponType::None;
}
else
{
lara.Control.Weapon.LastGunType = LaraWeaponType::Pistol;
}
}
if (HolsterType::Hips == info.Holster && lara.Control.Weapon.HolsterInfo.LeftHolster == HolsterSlotForWeapon(info.LaraWeaponType))
{
lara.Control.Weapon.HolsterInfo.LeftHolster = HolsterSlot::Empty;
lara.Control.Weapon.HolsterInfo.RightHolster = HolsterSlot::Empty;
}
else if (HolsterType::Back == info.Holster && lara.Control.Weapon.HolsterInfo.BackHolster == HolsterSlotForWeapon(info.LaraWeaponType))
{
lara.Control.Weapon.HolsterInfo.BackHolster = HolsterSlot::Empty;
}
}
return true;
} }
// Adding a weapon will either give the player the weapon + an amount of ammo, or, // Adding a weapon will not give the player any ammo even if they already have the weapon
// if they already have the weapon, simply the ammo. bool TryAddingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID)
bool TryAddingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount)
{ {
return TryModifyWeapon(lara, objectID, amount, true); return TryModifyWeapon(lara, objectID, 1, ModificationType::Add);
} }
// Removing a weapon is the reverse of the above; it will remove the weapon (if it's there) and the amount of ammo. // Removing a weapon is the reverse of the above; it will remove the weapon (if it's there).
bool TryRemovingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount) bool TryRemovingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID)
{ {
return TryModifyWeapon(lara, objectID, amount, false); return TryModifyWeapon(lara, objectID, 1, ModificationType::Remove);
} }
std::optional<bool> HasWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID) std::optional<bool> HasWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID)

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
enum class ModificationType;
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
struct LaraInfo; struct LaraInfo;
bool TryAddingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount = 0); bool TryAddingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID);
bool TryRemovingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, int amount = 0); bool TryRemovingWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID);
bool TryModifyWeapon(LaraInfo& lara, GAME_OBJECT_ID objectID, std::optional<int> count, ModificationType type);
std::optional<bool> HasWeapon(LaraInfo&, GAME_OBJECT_ID objectID); std::optional<bool> HasWeapon(LaraInfo&, GAME_OBJECT_ID objectID);

View file

@ -2,6 +2,13 @@
enum GAME_OBJECT_ID : short; enum GAME_OBJECT_ID : short;
enum class ModificationType
{
Add,
Remove,
Set
};
// Given an array and an Object ID, iterate through the array until we find // Given an array and an Object ID, iterate through the array until we find
// an ID that matches the ID we've passed in. // an ID that matches the ID we've passed in.
template <typename T, size_t N> int GetArraySlot(std::array<T, N> const& arr, GAME_OBJECT_ID objectID) template <typename T, size_t N> int GetArraySlot(std::array<T, N> const& arr, GAME_OBJECT_ID objectID)

View file

@ -42,6 +42,7 @@ struct ROOM_LIGHT
struct MESH_INFO struct MESH_INFO
{ {
PHD_3DPOS pos; PHD_3DPOS pos;
int roomNumber;
float scale; float scale;
short staticNumber; short staticNumber;
short flags; short flags;

View file

@ -818,7 +818,7 @@ bool SaveGame::Save(int slot)
staticMesh.add_flags(room->mesh[j].flags); staticMesh.add_flags(room->mesh[j].flags);
staticMesh.add_hit_points(room->mesh[j].HitPoints); staticMesh.add_hit_points(room->mesh[j].HitPoints);
staticMesh.add_room_number(i); staticMesh.add_room_number(room->mesh[j].roomNumber);
staticMesh.add_number(j); staticMesh.add_number(j);
staticMeshes.push_back(staticMesh.Finish()); staticMeshes.push_back(staticMesh.Finish());
} }
@ -1027,43 +1027,45 @@ bool SaveGame::Save(int slot)
alternatePendulumOffset = alternatePendulumInfo.Finish(); alternatePendulumOffset = alternatePendulumInfo.Finish();
} }
std::vector<flatbuffers::Offset<Save::ScriptTable>> levelTableVec;
std::vector<flatbuffers::Offset<flatbuffers::String>> levelStringVec2;
//std::vector<savedTable> savedTables;
std::vector<std::string> savedStrings;
std::vector<SavedVar> savedVars; std::vector<SavedVar> savedVars;
g_GameScript->GetVariables(savedVars); g_GameScript->GetVariables(savedVars);
std::vector<flatbuffers::Offset<Save::UnionTable>> varsVec; std::vector<flatbuffers::Offset<Save::UnionTable>> varsVec;
for (auto const& s : savedVars) for (auto const& s : savedVars)
{ {
flatbuffers::Offset<Save::ScriptTable> scriptTableOffset; auto putDataInVec = [&varsVec, &fbb](Save::VarUnion type, auto const & offsetVar)
flatbuffers::Offset<Save::stringTable> strOffset; {
flatbuffers::Offset<Save::doubleTable> doubleOffset; Save::UnionTableBuilder ut{ fbb };
flatbuffers::Offset<Save::boolTable> boolOffset; ut.add_u_type(type);
ut.add_u(offsetVar.Union());
varsVec.push_back(ut.Finish());
};
if (std::holds_alternative<std::string>(s)) if (std::holds_alternative<std::string>(s))
{ {
auto strOffset2 = fbb.CreateString(std::get<std::string>(s)); auto strOffset2 = fbb.CreateString(std::get<std::string>(s));
Save::stringTableBuilder stb{ fbb }; Save::stringTableBuilder stb{ fbb };
stb.add_str(strOffset2); stb.add_str(strOffset2);
strOffset = stb.Finish(); auto strOffset = stb.Finish();
putDataInVec(Save::VarUnion::str, strOffset);
} }
else if (std::holds_alternative<double>(s)) else if (std::holds_alternative<double>(s))
{ {
Save::doubleTableBuilder dtb{ fbb }; Save::doubleTableBuilder dtb{ fbb };
dtb.add_scalar(std::get<double>(s)); dtb.add_scalar(std::get<double>(s));
doubleOffset = dtb.Finish(); auto doubleOffset = dtb.Finish();
putDataInVec(Save::VarUnion::num, doubleOffset);
} }
else if (std::holds_alternative<bool>(s)) else if (std::holds_alternative<bool>(s))
{ {
Save::boolTableBuilder btb{ fbb }; Save::boolTableBuilder btb{ fbb };
btb.add_scalar(std::get<bool>(s)); btb.add_scalar(std::get<bool>(s));
boolOffset = btb.Finish(); auto boolOffset = btb.Finish();
putDataInVec(Save::VarUnion::boolean, boolOffset);
} }
else if (std::holds_alternative<IndexTable>(s)) else if (std::holds_alternative<IndexTable>(s))
{ {
@ -1077,37 +1079,43 @@ bool SaveGame::Save(int slot)
auto vecOffset = fbb.CreateVectorOfStructs(keyValVec); auto vecOffset = fbb.CreateVectorOfStructs(keyValVec);
Save::ScriptTableBuilder stb{ fbb }; Save::ScriptTableBuilder stb{ fbb };
stb.add_keys_vals(vecOffset); stb.add_keys_vals(vecOffset);
scriptTableOffset = stb.Finish(); auto scriptTableOffset = stb.Finish();
}
Save::UnionTableBuilder ut{ fbb }; putDataInVec(Save::VarUnion::tab, scriptTableOffset);
if (std::holds_alternative<std::string>(s))
{
ut.add_u_type(Save::VarUnion::str);
ut.add_u(strOffset.Union());
} }
else if (std::holds_alternative<double>(s)) else if (std::holds_alternative<FuncName>(s))
{ {
ut.add_u_type(Save::VarUnion::num); std::string data = std::get<FuncName>(s).name;
ut.add_u(doubleOffset.Union()); auto strOffset = fbb.CreateString(data);
Save::funcNameTableBuilder ftb{ fbb };
ftb.add_str(strOffset);
auto funcNameOffset = ftb.Finish();
putDataInVec(Save::VarUnion::funcName, funcNameOffset);
} }
else if (std::holds_alternative<bool>(s)) else if (std::holds_alternative<Vector3Int>(s))
{ {
ut.add_u_type(Save::VarUnion::boolean); Save::vec3TableBuilder vtb{ fbb };
ut.add_u(boolOffset.Union()); Vector3Int data = std::get<Vector3Int>(s);
Save::Vector3 saveVec = FromVector3(std::get<Vector3Int>(s));
vtb.add_vec(&saveVec);
auto vec3Offset = vtb.Finish();
putDataInVec(Save::VarUnion::vec3, vec3Offset);
} }
else if (std::holds_alternative<IndexTable>(s))
{
ut.add_u_type(Save::VarUnion::tab);
ut.add_u(scriptTableOffset.Union());
}
varsVec.push_back(ut.Finish());
} }
auto unionVec = fbb.CreateVector(varsVec); auto unionVec = fbb.CreateVector(varsVec);
Save::UnionVecBuilder uvb{ fbb }; Save::UnionVecBuilder uvb{ fbb };
uvb.add_members(unionVec); uvb.add_members(unionVec);
auto unionVecOffset = uvb.Finish(); auto unionVecOffset = uvb.Finish();
std::vector<std::string> callbackVecPreControl;
std::vector<std::string> callbackVecPostControl;
g_GameScript->GetCallbackStrings(callbackVecPreControl, callbackVecPostControl);
auto stringsCallbackPreControl = fbb.CreateVectorOfStrings(callbackVecPreControl);
auto stringsCallbackPostControl = fbb.CreateVectorOfStrings(callbackVecPostControl);
Save::SaveGameBuilder sgb{ fbb }; Save::SaveGameBuilder sgb{ fbb };
sgb.add_header(headerOffset); sgb.add_header(headerOffset);
@ -1151,6 +1159,8 @@ bool SaveGame::Save(int slot)
} }
sgb.add_script_vars(unionVecOffset); sgb.add_script_vars(unionVecOffset);
sgb.add_callbacks_pre_control(stringsCallbackPreControl);
sgb.add_callbacks_post_control(stringsCallbackPostControl);
auto sg = sgb.Finish(); auto sg = sgb.Finish();
fbb.Finish(sg); fbb.Finish(sg);
@ -1235,6 +1245,7 @@ bool SaveGame::Load(int slot)
int number = staticMesh->number(); int number = staticMesh->number();
room->mesh[number].pos = ToPHD(staticMesh->pose()); room->mesh[number].pos = ToPHD(staticMesh->pose());
room->mesh[number].roomNumber = staticMesh->room_number();
room->mesh[number].scale = staticMesh->scale(); room->mesh[number].scale = staticMesh->scale();
room->mesh[number].color = ToVector4(staticMesh->color()); room->mesh[number].color = ToVector4(staticMesh->color());
@ -1294,9 +1305,6 @@ bool SaveGame::Load(int slot)
ZeroMemory(&Lara, sizeof(LaraInfo)); ZeroMemory(&Lara, sizeof(LaraInfo));
// Items
InitialiseItemArray(NUM_ITEMS);
NextItemFree = s->next_item_free(); NextItemFree = s->next_item_free();
NextItemActive = s->next_item_active(); NextItemActive = s->next_item_active();
@ -1918,17 +1926,38 @@ bool SaveGame::Load(int slot)
{ {
auto tab = var->u_as_tab()->keys_vals(); auto tab = var->u_as_tab()->keys_vals();
auto& loadedTab = loadedVars.emplace_back(IndexTable{}); auto& loadedTab = loadedVars.emplace_back(IndexTable{});
for (auto const& p : *tab) for (auto const& p : *tab)
{ {
std::get<IndexTable>(loadedTab).push_back(std::make_pair(p->key(), p->val())); std::get<IndexTable>(loadedTab).push_back(std::make_pair(p->key(), p->val()));
} }
} }
else if (var->u_type() == Save::VarUnion::vec3)
{
loadedVars.push_back(ToVector3Int(var->u_as_vec3()->vec()));
}
else if (var->u_type() == Save::VarUnion::funcName)
{
loadedVars.push_back(FuncName{var->u_as_funcName()->str()->str()});
}
} }
} }
g_GameScript->SetVariables(loadedVars); g_GameScript->SetVariables(loadedVars);
std::vector<std::string> callbacksPreControlVec;
auto callbacksPreControlOffsetVec = s->callbacks_pre_control();
for (auto const& s : *callbacksPreControlOffsetVec)
callbacksPreControlVec.push_back(s->str());
std::vector<std::string> callbacksPostControlVec;
auto callbacksPostControlOffsetVec = s->callbacks_post_control();
for (auto const& s : *callbacksPostControlOffsetVec)
callbacksPostControlVec.push_back(s->str());
g_GameScript->SetCallbackStrings(callbacksPreControlVec, callbacksPostControlVec);
return true; return true;
} }

View file

@ -259,7 +259,7 @@ namespace TEN::Entities::Effects
return; return;
} }
if (ItemNearLara(&fx->pos, 200)) if (ItemNearLara(&fx->pos.Position, 200))
{ {
LaraItem->HitStatus = true; LaraItem->HitStatus = true;
if (fx->flag1 != 6) if (fx->flag1 != 6)

View file

@ -154,7 +154,7 @@ namespace TEN::Entities::Effects
SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose); SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose);
if (!Lara.Burn && if (!Lara.Burn &&
ItemNearLara(&item->Pose, 600) && ItemNearLara(&item->Pose.Position, 600) &&
(pow(LaraItem->Pose.Position.x - item->Pose.Position.x, 2) + (pow(LaraItem->Pose.Position.x - item->Pose.Position.x, 2) +
pow(LaraItem->Pose.Position.z - item->Pose.Position.z, 2) < pow(SECTOR(0.5f), 2)) && pow(LaraItem->Pose.Position.z - item->Pose.Position.z, 2) < pow(SECTOR(0.5f), 2)) &&
Lara.Control.WaterStatus != WaterStatus::FlyCheat) Lara.Control.WaterStatus != WaterStatus::FlyCheat)
@ -625,8 +625,7 @@ namespace TEN::Entities::Effects
TriggerDynamicLight(x, item->Pose.Position.y, z, 12, (GetRandomControl() & 0x3F) + 192, ((GetRandomControl() >> 4) & 0x1F) + 96, 0); TriggerDynamicLight(x, item->Pose.Position.y, z, 12, (GetRandomControl() & 0x3F) + 192, ((GetRandomControl() >> 4) & 0x1F) + 96, 0);
auto pos = PHD_3DPOS(item->Pose.Position); auto pos = item->Pose.Position;
if (ItemNearLara(&pos, 600)) if (ItemNearLara(&pos, 600))
{ {
if ((!Lara.Burn) && Lara.Control.WaterStatus != WaterStatus::FlyCheat) if ((!Lara.Burn) && Lara.Control.WaterStatus != WaterStatus::FlyCheat)

View file

@ -191,7 +191,7 @@ namespace TEN::Entities::TR4
locust->pos.Position.y += locust->randomRotation * phd_sin(-locust->pos.Orientation.x); locust->pos.Position.y += locust->randomRotation * phd_sin(-locust->pos.Orientation.x);
locust->pos.Position.z += locust->randomRotation * phd_cos(locust->pos.Orientation.x) * phd_cos(locust->pos.Orientation.y); locust->pos.Position.z += locust->randomRotation * phd_cos(locust->pos.Orientation.x) * phd_cos(locust->pos.Orientation.y);
if (ItemNearTarget(&locust->pos, LaraItem, CLICK(1) / 2)) if (ItemNearTarget(&locust->pos.Position, LaraItem, CLICK(1) / 2))
{ {
TriggerBlood(locust->pos.Position.x, locust->pos.Position.y, locust->pos.Position.z, 2 * GetRandomControl(), 2); TriggerBlood(locust->pos.Position.x, locust->pos.Position.y, locust->pos.Position.z, 2 * GetRandomControl(), 2);
DoDamage(LaraItem, LOCUST_LARA_DAMAGE); DoDamage(LaraItem, LOCUST_LARA_DAMAGE);

View file

@ -15,19 +15,23 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto APE_ATTACK_DAMAGE = 200; constexpr auto APE_ATTACK_DAMAGE = 200;
constexpr auto APE_ATTACK_RANGE = SQUARE(SECTOR(0.42f)); constexpr auto APE_ATTACK_RANGE = SQUARE(SECTOR(0.42f));
constexpr auto APE_PANIC_RANGE = SQUARE(SECTOR(2)); constexpr auto APE_PANIC_RANGE = SQUARE(SECTOR(2));
constexpr auto APE_JUMP_CHANCE = 0xA0; constexpr auto APE_IDLE_JUMP_CHANCE = 1.0f / 6;
constexpr auto APE_POUND_CHEST_CHANCE = APE_JUMP_CHANCE + 0xA0; constexpr auto APE_IDLE_POUND_CHEST_CHANCE = 1.0f / 3;
constexpr auto APE_POUND_GROUND_CHANCE = APE_POUND_CHEST_CHANCE + 0xA0; constexpr auto APE_IDLE_POUND_GROUND_CHANCE = 1.0f / 2;
constexpr auto APE_RUN_LEFT_CHANCE = APE_POUND_GROUND_CHANCE + 0xA0; constexpr auto APE_IDLE_RUN_LEFT_CHANCE = 1.0f / 2;
constexpr auto APE_RUN_JUMP_CHANCE = APE_IDLE_JUMP_CHANCE / 32;
constexpr auto APE_RUN_POUND_CHEST_CHANCE = APE_IDLE_POUND_CHEST_CHANCE / 32;
constexpr auto APE_RUN_POUND_GROUND_CHANCE = APE_IDLE_POUND_GROUND_CHANCE / 32;
constexpr auto APE_RUN_RUN_LEFT_CHANCE = APE_IDLE_RUN_LEFT_CHANCE / 32;
constexpr auto SHIFT = 75; constexpr auto APE_SHIFT = 75;
#define APE_RUN_TURN_RATE_MAX ANGLE(5.0f) #define APE_RUN_TURN_RATE_MAX ANGLE(5.0f)
#define APE_DISPLAY_ANGLE ANGLE(45.0f) #define APE_DISPLAY_ANGLE ANGLE(45.0f)
@ -118,12 +122,12 @@ namespace TEN::Entities::TR1
if (yy < yFloor) if (yy < yFloor)
{ {
item->Pose.Position.x = (yFloor * SECTOR(1)) - SHIFT; item->Pose.Position.x = (yFloor * SECTOR(1)) - APE_SHIFT;
item->Pose.Orientation.y = ANGLE(90.0f); item->Pose.Orientation.y = ANGLE(90.0f);
} }
else else
{ {
item->Pose.Position.x = (yy * SECTOR(1)) + SHIFT; item->Pose.Position.x = (yy * SECTOR(1)) + APE_SHIFT;
item->Pose.Orientation.y = -ANGLE(90.0f); item->Pose.Orientation.y = -ANGLE(90.0f);
} }
} }
@ -131,12 +135,12 @@ namespace TEN::Entities::TR1
{ {
if (xx < xFloor) if (xx < xFloor)
{ {
item->Pose.Position.z = (xFloor * SECTOR(1)) - SHIFT; item->Pose.Position.z = (xFloor * SECTOR(1)) - APE_SHIFT;
item->Pose.Orientation.y = 0; item->Pose.Orientation.y = 0;
} }
else else
{ {
item->Pose.Position.z = (xx * SECTOR(1)) + SHIFT; item->Pose.Position.z = (xx * SECTOR(1)) + APE_SHIFT;
item->Pose.Orientation.y = -ANGLE(180.0f); item->Pose.Orientation.y = -ANGLE(180.0f);
} }
} }
@ -145,7 +149,7 @@ namespace TEN::Entities::TR1
// diagonal // diagonal
} }
if (CreatureVault(itemNumber, angle, 2, SHIFT) == 2) if (CreatureVault(itemNumber, angle, 2, APE_SHIFT) == 2)
{ {
item->Pose.Position.y = y; item->Pose.Position.y = y;
SetAnimation(item, APE_ANIM_VAULT); SetAnimation(item, APE_ANIM_VAULT);
@ -184,8 +188,6 @@ namespace TEN::Entities::TR1
if (item->HitStatus || AI.distance < APE_PANIC_RANGE) if (item->HitStatus || AI.distance < APE_PANIC_RANGE)
creatureInfo->Flags |= APE_FLAG_ATTACK; creatureInfo->Flags |= APE_FLAG_ATTACK;
short random;
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case APE_STATE_IDLE: case APE_STATE_IDLE:
@ -207,14 +209,13 @@ namespace TEN::Entities::TR1
else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) && else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) &&
AI.zoneNumber == AI.enemyZone && AI.ahead) AI.zoneNumber == AI.enemyZone && AI.ahead)
{ {
random = (short)(GetRandomControl() / 32); if (TestProbability(APE_IDLE_JUMP_CHANCE))
if (random < APE_JUMP_CHANCE)
item->Animation.TargetState = APE_STATE_JUMP; item->Animation.TargetState = APE_STATE_JUMP;
else if (random < APE_POUND_CHEST_CHANCE) else if (TestProbability(APE_IDLE_POUND_CHEST_CHANCE))
item->Animation.TargetState = APE_STATE_POUND_CHEST; item->Animation.TargetState = APE_STATE_POUND_CHEST;
else if (random < APE_POUND_GROUND_CHANCE) else if (TestProbability(APE_IDLE_POUND_GROUND_CHANCE))
item->Animation.TargetState = APE_STATE_POUND_GROUND; item->Animation.TargetState = APE_STATE_POUND_GROUND;
else if (random < APE_RUN_LEFT_CHANCE) else if (TestProbability(APE_IDLE_RUN_LEFT_CHANCE))
{ {
item->Animation.TargetState = APE_STATE_RUN_LEFT; item->Animation.TargetState = APE_STATE_RUN_LEFT;
creatureInfo->MaxTurn = 0; creatureInfo->MaxTurn = 0;
@ -246,18 +247,17 @@ namespace TEN::Entities::TR1
} }
else if (creatureInfo->Mood != MoodType::Escape) else if (creatureInfo->Mood != MoodType::Escape)
{ {
random = (short)GetRandomControl(); if (TestProbability(APE_RUN_JUMP_CHANCE))
if (random < APE_JUMP_CHANCE)
{ {
item->Animation.RequiredState = APE_STATE_JUMP; item->Animation.RequiredState = APE_STATE_JUMP;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;
} }
else if (random < APE_POUND_CHEST_CHANCE) else if (TestProbability(APE_RUN_POUND_CHEST_CHANCE))
{ {
item->Animation.RequiredState = APE_STATE_POUND_CHEST; item->Animation.RequiredState = APE_STATE_POUND_CHEST;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;
} }
else if (random < APE_POUND_GROUND_CHANCE) else if (TestProbability(APE_RUN_POUND_GROUND_CHANCE))
{ {
item->Animation.RequiredState = APE_STATE_POUND_GROUND; item->Animation.RequiredState = APE_STATE_POUND_GROUND;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void ApeControl(short itemNumber); void ApeControl(short itemNumber);
} }

View file

@ -9,11 +9,13 @@
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
#include "Game/misc.h" #include "Game/misc.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto BEAR_RUN_DAMAGE = 3; constexpr auto BEAR_RUN_DAMAGE = 3;
constexpr auto BEAR_ATTACK_DAMAGE = 200; constexpr auto BEAR_ATTACK_DAMAGE = 200;
@ -24,10 +26,10 @@ namespace TEN::Entities::TR1
constexpr auto BEAR_REAR_RANGE = SECTOR(2); constexpr auto BEAR_REAR_RANGE = SECTOR(2);
constexpr auto BEAR_REAR_SWIPE_ATTACK_RANGE = SECTOR(0.6f); constexpr auto BEAR_REAR_SWIPE_ATTACK_RANGE = SECTOR(0.6f);
constexpr auto BEAR_EAT_RANGE = CLICK(3); constexpr auto BEAR_EAT_RANGE = CLICK(3);
constexpr auto BEAR_ROAR_CHANCE = 0x50; constexpr auto BEAR_ROAR_CHANCE = 1.0f / 400;
constexpr auto BEAR_REAR_CHANCE = 0x300; constexpr auto BEAR_REAR_CHANCE = 1.0f / 40;
constexpr auto BEAR_DROP_CHANCE = 0x600; constexpr auto BEAR_DROP_CHANCE = 1.0f / 22;
#define BEAR_WALK_TURN_RATE_MAX ANGLE(2.0f) #define BEAR_WALK_TURN_RATE_MAX ANGLE(2.0f)
#define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f) #define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f)
@ -81,8 +83,8 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -117,8 +119,8 @@ namespace TEN::Entities::TR1
{ {
if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints)) if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints))
{ {
creature->Flags = 0;
DoDamage(creature->Enemy, BEAR_SLAM_DAMAGE); DoDamage(creature->Enemy, BEAR_SLAM_DAMAGE);
creature->Flags = 0;
} }
break; break;
@ -141,12 +143,12 @@ namespace TEN::Entities::TR1
if (item->HitStatus) if (item->HitStatus)
creature->Flags = 1; creature->Flags = 1;
const bool laraDead = LaraItem->HitPoints <= 0; bool isLaraDead = LaraItem->HitPoints <= 0;
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case BEAR_STATE_IDLE: case BEAR_STATE_IDLE:
if (laraDead) if (isLaraDead)
{ {
if (AI.bite && AI.distance < pow(BEAR_EAT_RANGE, 2)) if (AI.bite && AI.distance < pow(BEAR_EAT_RANGE, 2))
item->Animation.TargetState = BEAR_STATE_EAT; item->Animation.TargetState = BEAR_STATE_EAT;
@ -165,7 +167,7 @@ namespace TEN::Entities::TR1
case BEAR_STATE_STROLL: case BEAR_STATE_STROLL:
creature->MaxTurn = BEAR_WALK_TURN_RATE_MAX; creature->MaxTurn = BEAR_WALK_TURN_RATE_MAX;
if (laraDead && item->TestBits(JointBitType::Touch, BearAttackJoints) && AI.ahead) if (isLaraDead && item->TestBits(JointBitType::Touch, BearAttackJoints) && AI.ahead)
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
else if (creature->Mood != MoodType::Bored) else if (creature->Mood != MoodType::Bored)
{ {
@ -174,10 +176,10 @@ namespace TEN::Entities::TR1
if (creature->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
item->Animation.RequiredState = BEAR_STATE_STROLL; item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (GetRandomControl() < BEAR_ROAR_CHANCE) else if (TestProbability(BEAR_ROAR_CHANCE))
{ {
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
item->Animation.RequiredState = BEAR_STATE_ROAR;
} }
break; break;
@ -186,16 +188,14 @@ namespace TEN::Entities::TR1
creature->MaxTurn = BEAR_RUN_TURN_RATE_MAX; creature->MaxTurn = BEAR_RUN_TURN_RATE_MAX;
if (item->TestBits(JointBitType::Touch, BearAttackJoints)) if (item->TestBits(JointBitType::Touch, BearAttackJoints))
{
DoDamage(creature->Enemy, BEAR_RUN_DAMAGE); DoDamage(creature->Enemy, BEAR_RUN_DAMAGE);
}
if (creature->Mood == MoodType::Bored || laraDead) if (creature->Mood == MoodType::Bored || isLaraDead)
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.ahead && !item->Animation.RequiredState) else if (AI.ahead && !item->Animation.RequiredState)
{ {
if (AI.distance < pow(BEAR_REAR_RANGE, 2) && if (AI.distance < pow(BEAR_REAR_RANGE, 2) &&
GetRandomControl() < BEAR_REAR_CHANCE && TestProbability(BEAR_REAR_CHANCE) &&
!creature->Flags) !creature->Flags)
{ {
item->Animation.RequiredState = BEAR_STATE_REAR; item->Animation.RequiredState = BEAR_STATE_REAR;
@ -227,8 +227,8 @@ namespace TEN::Entities::TR1
case BEAR_STATE_WALK_FORWARD: case BEAR_STATE_WALK_FORWARD:
if (creature->Flags) if (creature->Flags)
{ {
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (AI.ahead && item->TestBits(JointBitType::Touch, BearAttackJoints)) else if (AI.ahead && item->TestBits(JointBitType::Touch, BearAttackJoints))
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
@ -237,15 +237,15 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL; item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (creature->Mood == MoodType::Bored || GetRandomControl() < BEAR_ROAR_CHANCE) else if (creature->Mood == MoodType::Bored || TestProbability(BEAR_ROAR_CHANCE))
{ {
item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_ROAR; item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_REAR;
} }
else if (AI.distance > pow(BEAR_REAR_RANGE, 2) || GetRandomControl() < BEAR_DROP_CHANCE) else if (AI.distance > pow(BEAR_REAR_RANGE, 2) || TestProbability(BEAR_DROP_CHANCE))
{ {
item->Animation.RequiredState = BEAR_STATE_IDLE;
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_IDLE;
} }
break; break;
@ -264,8 +264,8 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, BearAttackJoints)) item->TestBits(JointBitType::Touch, BearAttackJoints))
{ {
CreatureEffect(item, BearBite, DoBloodSplat);
DoDamage(creature->Enemy, BEAR_ATTACK_DAMAGE); DoDamage(creature->Enemy, BEAR_ATTACK_DAMAGE);
CreatureEffect(item, BearBite, DoBloodSplat);
item->Animation.RequiredState = BEAR_STATE_IDLE; item->Animation.RequiredState = BEAR_STATE_IDLE;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void BearControl(short itemNumber); void BearControl(short itemNumber);
} }

View file

@ -16,7 +16,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto BIG_RAT_BITE_ATTACK_DAMAGE = 20; constexpr auto BIG_RAT_BITE_ATTACK_DAMAGE = 20;
constexpr auto BIG_RAT_POUNCE_ATTACK_DAMAGE = 25; constexpr auto BIG_RAT_POUNCE_ATTACK_DAMAGE = 25;
@ -27,7 +27,7 @@ namespace TEN::Entities::TR1
constexpr auto BIG_RAT_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(0.5f)); constexpr auto BIG_RAT_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(0.5f));
constexpr auto BIG_RAT_WATER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.3f)); constexpr auto BIG_RAT_WATER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.3f));
constexpr auto BIG_RAT_REAR_POSE_CHANCE = 0.008f; constexpr auto BIG_RAT_REAR_POSE_CHANCE = 1.0f / 128;
constexpr auto BIG_RAT_SWIM_UP_DOWN_SPEED = 32; constexpr auto BIG_RAT_SWIM_UP_DOWN_SPEED = 32;
constexpr auto BIG_RAT_WATER_SURFACE_OFFSET = 10; constexpr auto BIG_RAT_WATER_SURFACE_OFFSET = 10;
@ -115,10 +115,11 @@ namespace TEN::Entities::TR1
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
int waterHeight = GetRatWaterHeight(item); int waterHeight = GetRatWaterHeight(item);
short head = 0;
short angle = 0;
bool isOnWater = waterHeight != NO_HEIGHT; bool isOnWater = waterHeight != NO_HEIGHT;
short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH && if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH &&
@ -183,9 +184,9 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && AI.ahead && if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatBite.meshNum)) item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{ {
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE); DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat); CreatureEffect(item, BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
} }
break; break;
@ -194,9 +195,9 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && AI.ahead && if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatBite.meshNum)) item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{ {
item->Animation.RequiredState = BIG_RAT_STATE_RUN_FORWARD;
DoDamage(creature->Enemy, BIG_RAT_POUNCE_ATTACK_DAMAGE); DoDamage(creature->Enemy, BIG_RAT_POUNCE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat); CreatureEffect(item, BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_RUN_FORWARD;
} }
break; break;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseBigRat(short itemNumber); void InitialiseBigRat(short itemNumber);
void BigRatControl(short itemNumber); void BigRatControl(short itemNumber);

View file

@ -15,21 +15,23 @@
#include "Game/people.h" #include "Game/people.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto CENTAUR_REAR_DAMAGE = 200; constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f); constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f);
constexpr auto CENTAUR_REAR_CHANCE = 0x60; constexpr auto CENTAUR_REAR_CHANCE = 1.0f / 340;
constexpr auto CENTAUR_BOMB_VELOCITY = 20; constexpr auto CENTAUR_BOMB_VELOCITY = 20;
#define CENTAUR_TURN_RATE_MAX ANGLE(4.0f) #define CENTAUR_TURN_RATE_MAX ANGLE(4.0f)
const auto CentaurRocketBite = BiteInfo(Vector3(11.0f, 415.0f, 41.0f), 13); const auto CentaurRocketBite = BiteInfo(Vector3(11.0f, 415.0f, 41.0f), 13);
const auto CentaurRearBite = BiteInfo(Vector3(50.0f, 30.0f, 0.0f), 5); const auto CentaurRearBite = BiteInfo(Vector3(50.0f, 30.0f, 0.0f), 5);
const vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 }; const vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 };
enum CentaurState enum CentaurState
@ -56,17 +58,14 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != CENTAUR_STATE_DEATH) if (item->Animation.ActiveState != CENTAUR_STATE_DEATH)
{ SetAnimation(item, CENTAUR_ANIM_DEATH);
item->Animation.AnimNumber = Objects[ID_CENTAUR_MUTANT].animIndex + CENTAUR_ANIM_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = CENTAUR_STATE_DEATH;
}
} }
else else
{ {
@ -98,18 +97,18 @@ namespace TEN::Entities::TR1
case CENTAUR_STATE_RUN_FORWARD: case CENTAUR_STATE_RUN_FORWARD:
if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2)) if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2))
{ {
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE; item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
} }
else if (Targetable(item, &AI)) else if (Targetable(item, &AI))
{ {
item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_AIM; item->Animation.RequiredState = CENTAUR_STATE_AIM;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
} }
else if (GetRandomControl() < CENTAUR_REAR_CHANCE) else if (TestProbability(CENTAUR_REAR_CHANCE))
{ {
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE; item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
} }
break; break;
@ -137,8 +136,8 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, CentaurAttackJoints)) item->TestBits(JointBitType::Touch, CentaurAttackJoints))
{ {
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE); DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE);
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
item->Animation.RequiredState = CENTAUR_STATE_IDLE; item->Animation.RequiredState = CENTAUR_STATE_IDLE;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto SHARD_VELOCITY = 250; constexpr auto SHARD_VELOCITY = 250;
constexpr auto BOMB_VELOCITY = 220; constexpr auto BOMB_VELOCITY = 220;

View file

@ -14,7 +14,7 @@
// - Bacon Lara cannot be targeted. // - Bacon Lara cannot be targeted.
// - Bacon Lara cannot move like Lara. // - Bacon Lara cannot move like Lara.
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
// Original: // Original:
void InitialiseDoppelganger(short itemNumber) void InitialiseDoppelganger(short itemNumber)
@ -80,26 +80,24 @@ namespace TEN::Entities::TR1
int laraFloorHeight = GetCollision(LaraItem).Position.Floor; int laraFloorHeight = GetCollision(LaraItem).Position.Floor;
// Animate bacon Lara, mirroring Lara's position. // Animate bacon Lara, mirroring Lara's position.
item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Animation.AnimNumber = LaraItem->Animation.AnimNumber; item->Animation.AnimNumber = LaraItem->Animation.AnimNumber;
item->Pose.Position.x = pos.x; item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Pose.Position.y = pos.y; item->Pose.Position = pos;
item->Pose.Position.z = pos.z;
item->Pose.Orientation.x = LaraItem->Pose.Orientation.x; item->Pose.Orientation.x = LaraItem->Pose.Orientation.x;
item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f); item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f);
item->Pose.Orientation.z = LaraItem->Pose.Orientation.z; item->Pose.Orientation.z = LaraItem->Pose.Orientation.z;
ItemNewRoom(itemNumber, LaraItem->RoomNumber); ItemNewRoom(itemNumber, LaraItem->RoomNumber);
// Compare floor heights. // Compare floor heights.
if (item->Floor >= laraFloorHeight + SECTOR(1) + 1 && // Add 1 to avoid bacon Lara dying when exiting water. if (item->Floor >= (laraFloorHeight + SECTOR(1) + 1) && // Add 1 to avoid bacon Lara dying when exiting water.
!LaraItem->Animation.IsAirborne) !LaraItem->Animation.IsAirborne)
{ {
SetAnimation(item, LA_JUMP_WALL_SMASH_START); SetAnimation(item, LA_JUMP_WALL_SMASH_START);
item->Animation.Velocity.z = 0;
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = true; item->Animation.IsAirborne = true;
item->Data = -1; item->Animation.Velocity.y = 0.0f;
item->Animation.Velocity.z = 0.0f;
item->Pose.Position.y += 50; item->Pose.Position.y += 50;
item->Data = -1;
} }
} }
@ -114,8 +112,8 @@ namespace TEN::Entities::TR1
item->Pose.Position.y = item->Floor; item->Pose.Position.y = item->Floor;
TestTriggers(item, true); TestTriggers(item, true);
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = false; item->Animation.IsAirborne = false;
item->Animation.Velocity.y = 0.0f;
item->Animation.TargetState = LS_DEATH; item->Animation.TargetState = LS_DEATH;
item->Animation.RequiredState = LS_DEATH; item->Animation.RequiredState = LS_DEATH;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseDoppelganger(short itemNumber); void InitialiseDoppelganger(short itemNumber);
void DoppelgangerControl(short itemNumber); void DoppelgangerControl(short itemNumber);

View file

@ -12,11 +12,13 @@
#include "Game/misc.h" #include "Game/misc.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto MUTANT_ATTACK_DAMAGE = 500; constexpr auto MUTANT_ATTACK_DAMAGE = 500;
constexpr auto MUTANT_CONTACT_DAMAGE = 6; constexpr auto MUTANT_CONTACT_DAMAGE = 6;
@ -24,16 +26,17 @@ namespace TEN::Entities::TR1
constexpr auto MUTANT_ATTACK_RANGE = SQUARE(SECTOR(2.5f)); constexpr auto MUTANT_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto MUTANT_CLOSE_RANGE = SQUARE(SECTOR(2.2f)); constexpr auto MUTANT_CLOSE_RANGE = SQUARE(SECTOR(2.2f));
constexpr auto MUTANT_ATTACK_1_CHANCE = 0x2AF8; // TODO: Unused.
constexpr auto MUTANT_ATTACK_2_CHANCE = 0x55F0; constexpr auto MUTANT_ATTACK_1_CHANCE = 1.0f / 3.0f;
constexpr auto MUTANT_ATTACK_2_CHANCE = MUTANT_ATTACK_1_CHANCE * 2;
#define MUTANT_NEED_TURN ANGLE(45.0f) #define MUTANT_NEED_TURN ANGLE(45.0f)
#define MUTANT_TURN ANGLE(3.0f) #define MUTANT_TURN ANGLE(3.0f)
#define LARA_GIANT_MUTANT_DEATH 6 // TODO: Not 13? Check this. #define LARA_GIANT_MUTANT_DEATH 6 // TODO: Not 13? Check this.
const vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; const vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
const vector<int> MutantAttackLeftJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; const vector<int> MutantAttackLeftJoint = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; const vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
enum GiantMutantState enum GiantMutantState
@ -66,17 +69,13 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != MUTANT_STATE_DEATH) if (item->Animation.ActiveState != MUTANT_STATE_DEATH)
{ SetAnimation(item, MUTANT_ANIM_DEATH);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + MUTANT_ANIM_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = MUTANT_STATE_DEATH;
}
} }
else else
{ {
@ -92,9 +91,7 @@ namespace TEN::Entities::TR1
angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y; angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y;
if (item->TouchBits) if (item->TouchBits)
{
DoDamage(creature->Enemy, MUTANT_CONTACT_DAMAGE); DoDamage(creature->Enemy, MUTANT_CONTACT_DAMAGE);
}
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
@ -122,7 +119,7 @@ namespace TEN::Entities::TR1
else else
item->Animation.TargetState = MUTANT_STATE_FORWARD; item->Animation.TargetState = MUTANT_STATE_FORWARD;
} }
else if (GetRandomControl() < 0x4000) else if (TestProbability(0.5f))
item->Animation.TargetState = MUTANT_STATE_ATTACK_1; item->Animation.TargetState = MUTANT_STATE_ATTACK_1;
else else
item->Animation.TargetState = MUTANT_STATE_ATTACK_2; item->Animation.TargetState = MUTANT_STATE_ATTACK_2;
@ -178,8 +175,8 @@ namespace TEN::Entities::TR1
case MUTANT_STATE_ATTACK_1: case MUTANT_STATE_ATTACK_1:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackRightJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackRightJoints))
{ {
creature->Flags = 1;
DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE); DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE);
creature->Flags = 1;
} }
break; break;
@ -187,8 +184,8 @@ namespace TEN::Entities::TR1
case MUTANT_STATE_ATTACK_2: case MUTANT_STATE_ATTACK_2:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackJoints))
{ {
creature->Flags = 1;
DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE); DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE);
creature->Flags = 1;
} }
break; break;
@ -202,14 +199,11 @@ namespace TEN::Entities::TR1
LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_GIANT_MUTANT_DEATH; LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_GIANT_MUTANT_DEATH;
LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase; LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase;
LaraItem->Animation.ActiveState = LaraItem->Animation.TargetState = 46; LaraItem->Animation.ActiveState = 46;
LaraItem->RoomNumber = item->RoomNumber; LaraItem->Animation.TargetState = 46;
LaraItem->Pose.Position.x = item->Pose.Position.x;
LaraItem->Pose.Position.y = item->Pose.Position.y;
LaraItem->Pose.Position.z = item->Pose.Position.z;
LaraItem->Pose.Orientation.y = item->Pose.Orientation.y;
LaraItem->Pose.Orientation.x = LaraItem->Pose.Orientation.z = 0;
LaraItem->Animation.IsAirborne = false; LaraItem->Animation.IsAirborne = false;
LaraItem->Pose = PHD_3DPOS(item->Pose.Position, 0, item->Pose.Orientation.y, 0);
LaraItem->RoomNumber = item->RoomNumber;
LaraItem->HitPoints = -1; LaraItem->HitPoints = -1;
Lara.Air = -1; Lara.Air = -1;
Lara.Control.HandStatus = HandStatus::Busy; Lara.Control.HandStatus = HandStatus::Busy;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void GiantMutantControl(short itemNumber); void GiantMutantControl(short itemNumber);
} }

View file

@ -10,9 +10,12 @@
#include "Game/people.h" #include "Game/people.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/trmath.h" #include "Specific/trmath.h"
namespace TEN::Entities::TR1 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR1
{ {
// TODO: Organise. // TODO: Organise.
constexpr auto NATLA_SHOT_DAMAGE = 100; constexpr auto NATLA_SHOT_DAMAGE = 100;
@ -20,9 +23,10 @@ namespace TEN::Entities::TR1
constexpr auto NATLA_DEATH_TIME = (FPS * 16); // 16 seconds. constexpr auto NATLA_DEATH_TIME = (FPS * 16); // 16 seconds.
constexpr auto NATLA_FLYMODE = 0x8000; constexpr auto NATLA_FLYMODE = 0x8000;
constexpr auto NATLA_TIMER = 0x7FFF; constexpr auto NATLA_TIMER = 0x7FFF;
constexpr auto NATLA_LAND_CHANCE = 0x100;
constexpr auto NATLA_GUN_VELOCITY = 400; constexpr auto NATLA_GUN_VELOCITY = 400;
constexpr auto NATLA_LAND_CHANCE = 0.008f;
#define NATLA_TURN_NEAR_DEATH_SPEED ANGLE(6.0f) #define NATLA_TURN_NEAR_DEATH_SPEED ANGLE(6.0f)
#define NATLA_TURN_SPEED ANGLE(5.0f) #define NATLA_TURN_SPEED ANGLE(5.0f)
#define NATLA_FLY_ANGLE_SPEED ANGLE(5.0f) #define NATLA_FLY_ANGLE_SPEED ANGLE(5.0f)
@ -183,7 +187,7 @@ namespace TEN::Entities::TR1
if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE)) if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE))
{ {
if (creature->Flags & NATLA_FLYMODE && shoot && GetRandomControl() < NATLA_LAND_CHANCE) if (creature->Flags & NATLA_FLYMODE && shoot && TestProbability(NATLA_LAND_CHANCE))
creature->Flags -= NATLA_FLYMODE; creature->Flags -= NATLA_FLYMODE;
if (!(creature->Flags & NATLA_FLYMODE)) if (!(creature->Flags & NATLA_FLYMODE))

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void NatlaControl(short itemNumber); void NatlaControl(short itemNumber);
} }

View file

@ -13,11 +13,13 @@
#include "Game/people.h" #include "Game/people.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/trmath.h" #include "Specific/trmath.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150; constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150;
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100; constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100;
@ -29,8 +31,8 @@ namespace TEN::Entities::TR1
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(SECTOR(2.5f)); constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto WINGED_MUTANT_ATTACK_RANGE = SQUARE(SECTOR(3.75f)); constexpr auto WINGED_MUTANT_ATTACK_RANGE = SQUARE(SECTOR(3.75f));
constexpr auto WINGED_MUTANT_POSE_CHANCE = 85; constexpr auto WINGED_MUTANT_POSE_CHANCE = 1.0f / 400;
constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 200; constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1.0f / 164;
constexpr auto WINGED_MUTANT_FLY_VELOCITY = CLICK(1) / 8; constexpr auto WINGED_MUTANT_FLY_VELOCITY = CLICK(1) / 8;
constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250; constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250;
@ -337,7 +339,7 @@ namespace TEN::Entities::TR1
if (AI.distance < WINGED_MUTANT_WALK_RANGE) if (AI.distance < WINGED_MUTANT_WALK_RANGE)
{ {
if (AI.zoneNumber == AI.enemyZone || if (AI.zoneNumber == AI.enemyZone ||
GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE) TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{ {
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
} }
@ -345,7 +347,7 @@ namespace TEN::Entities::TR1
else else
item->Animation.TargetState = WMUTANT_STATE_IDLE; item->Animation.TargetState = WMUTANT_STATE_IDLE;
} }
else if (creature->Mood == MoodType::Bored && GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE) else if (creature->Mood == MoodType::Bored && TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Attack || else if (creature->Mood == MoodType::Attack ||
creature->Mood == MoodType::Escape) creature->Mood == MoodType::Escape)
@ -363,7 +365,7 @@ namespace TEN::Entities::TR1
else if (creature->Mood == MoodType::Bored || else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && AI.zoneNumber != AI.enemyZone)) (creature->Mood == MoodType::Stalk && AI.zoneNumber != AI.enemyZone))
{ {
if (GetRandomControl() < WINGED_MUTANT_POSE_CHANCE) if (TestProbability(WINGED_MUTANT_POSE_CHANCE))
item->Animation.TargetState = WMUTANT_STATE_POSE; item->Animation.TargetState = WMUTANT_STATE_POSE;
} }
else if (creature->Mood == MoodType::Stalk && else if (creature->Mood == MoodType::Stalk &&

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseWingedMutant(short itemNumber); void InitialiseWingedMutant(short itemNumber);
void WingedMutantControl(short itemNumber); void WingedMutantControl(short itemNumber);

View file

@ -9,11 +9,13 @@
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
#include "Game/misc.h" #include "Game/misc.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto WOLF_BITE_DAMAGE = 100; constexpr auto WOLF_BITE_DAMAGE = 100;
constexpr auto WOLF_LUNGE_DAMAGE = 50; constexpr auto WOLF_LUNGE_DAMAGE = 50;
@ -21,9 +23,9 @@ namespace TEN::Entities::TR1
constexpr auto WOLF_ATTACK_RANGE = SQUARE(SECTOR(1.5f)); constexpr auto WOLF_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto WOLF_STALK_RANGE = SQUARE(SECTOR(2)); constexpr auto WOLF_STALK_RANGE = SQUARE(SECTOR(2));
constexpr auto WOLF_WAKE_CHANCE = 0x20; constexpr auto WOLF_WAKE_CHANCE = 1.0f / 1000;
constexpr auto WOLF_SLEEP_CHANCE = 0x20; constexpr auto WOLF_SLEEP_CHANCE = 1.0f / 1000;
constexpr auto WOLF_HOWL_CHANCE = 0x180; constexpr auto WOLF_HOWL_CHANCE = 1.0f / 85;
constexpr auto WOLF_SLEEP_FRAME = 96; constexpr auto WOLF_SLEEP_FRAME = 96;
@ -110,7 +112,7 @@ namespace TEN::Entities::TR1
item->Animation.RequiredState = WOLF_STATE_CROUCH; item->Animation.RequiredState = WOLF_STATE_CROUCH;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
} }
else if (GetRandomControl() < WOLF_WAKE_CHANCE) else if (TestProbability(WOLF_WAKE_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_WALK; item->Animation.RequiredState = WOLF_STATE_WALK;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
@ -133,7 +135,7 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = WOLF_STATE_STALK; item->Animation.TargetState = WOLF_STATE_STALK;
item->Animation.RequiredState = WOLF_STATE_NONE; item->Animation.RequiredState = WOLF_STATE_NONE;
} }
else if (GetRandomControl() < WOLF_SLEEP_CHANCE) else if (TestProbability(WOLF_SLEEP_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_SLEEP; item->Animation.RequiredState = WOLF_STATE_SLEEP;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
@ -174,7 +176,7 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = WOLF_STATE_RUN; item->Animation.TargetState = WOLF_STATE_RUN;
} }
} }
else if (GetRandomControl() < WOLF_HOWL_CHANCE) else if (TestProbability(WOLF_HOWL_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_HOWL; item->Animation.RequiredState = WOLF_STATE_HOWL;
item->Animation.TargetState = WOLF_STATE_CROUCH; item->Animation.TargetState = WOLF_STATE_CROUCH;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseWolf(short itemNumber); void InitialiseWolf(short itemNumber);
void WolfControl(short itemNumber); void WolfControl(short itemNumber);

View file

@ -1,7 +1,6 @@
#include "framework.h" #include "framework.h"
#include "Objects/TR1/tr1_objects.h" #include "Objects/TR1/tr1_objects.h"
/// necessary import
#include "Game/control/box.h" #include "Game/control/box.h"
#include "Game/collision/collide_item.h" #include "Game/collision/collide_item.h"
#include "Game/itemdata/creature_info.h" #include "Game/itemdata/creature_info.h"
@ -9,7 +8,7 @@
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Specific/level.h" #include "Specific/level.h"
/// entities // Creatures
#include "Objects/TR1/Entity/tr1_ape.h" // OK #include "Objects/TR1/Entity/tr1_ape.h" // OK
#include "Objects/TR1/Entity/tr1_bear.h" // OK #include "Objects/TR1/Entity/tr1_bear.h" // OK
#include "Objects/TR1/Entity/tr1_doppelganger.h" // OK #include "Objects/TR1/Entity/tr1_doppelganger.h" // OK
@ -21,7 +20,7 @@
#include "Objects/TR1/Entity/tr1_winged_mutant.h" #include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Objects/Utils/object_helper.h" #include "Objects/Utils/object_helper.h"
using namespace TEN::Entities::TR1; using namespace TEN::Entities::Creatures::TR1;
static void StartEntity(ObjectInfo* obj) static void StartEntity(ObjectInfo* obj)
{ {
@ -78,7 +77,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_APE; obj->ZoneType = ZoneType::Ape;
} }
obj = &Objects[ID_BIG_RAT]; obj = &Objects[ID_BIG_RAT];
@ -98,7 +97,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->waterCreature = true; obj->waterCreature = true;
obj->zoneType = ZONE_WATER; obj->ZoneType = ZoneType::Water;
obj->SetBoneRotation(1, ROT_Y); // head obj->SetBoneRotation(1, ROT_Y); // head
} }
@ -174,7 +173,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_BLOCKABLE; obj->ZoneType = ZoneType::Blockable;
obj->SetBoneRotation(10, ROT_X | ROT_Y); obj->SetBoneRotation(10, ROT_X | ROT_Y);
} }
@ -194,7 +193,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveFlags = true; obj->saveFlags = true;
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->savePosition = true; obj->savePosition = true;
obj->zoneType = ZONE_FLYER; obj->ZoneType = ZoneType::Flyer;
obj->SetBoneRotation(1, ROT_Y); // torso obj->SetBoneRotation(1, ROT_Y); // torso
obj->SetBoneRotation(2, ROT_Y); // head obj->SetBoneRotation(2, ROT_Y); // head
} }

Some files were not shown because too many files have changed in this diff Show more