Improved pathfinding logic

This commit is contained in:
Vladislav Khorev 2026-05-21 11:48:18 +03:00
parent 2659951d8e
commit 3a73f3fbde
36 changed files with 458337 additions and 871 deletions

260
PATHFINDING.md Normal file
View File

@ -0,0 +1,260 @@
# Pathfinding System
This document describes the grid-based pathfinding used for the player and all NPCs, including the collision avoidance and movement quality improvements.
---
## Table of Contents
1. [Grid Representation](#1-grid-representation)
2. [Building the Walkable Grid](#2-building-the-walkable-grid)
3. [A\* Path Search](#3-a-path-search)
4. [Path Smoothing](#4-path-smoothing)
5. [Approaching Unreachable Destinations](#5-approaching-unreachable-destinations)
6. [Dynamic Obstacles](#6-dynamic-obstacles)
7. [Path Following](#7-path-following)
8. [Character Collision Resolution](#8-character-collision-resolution)
9. [Dynamic Replanning](#9-dynamic-replanning)
10. [Key Constants Reference](#10-key-constants-reference)
---
## 1. Grid Representation
The world is divided into a uniform 2D grid in the XZ plane (Y is ignored during pathfinding; all characters walk on a flat floor at `floorY`).
Each cell is either **walkable** (`1`) or **blocked** (`0`). The grid is stored as a flat `std::vector<unsigned char>` indexed by `z * gridWidth + x`.
**Parameters** (all configurable in the JSON config file):
| Parameter | Default | Description |
|---|---|---|
| `cellSize` | 0.4 m | Width and depth of one cell |
| `agentRadius` | 0.45 m | Half-width of a character — used to erode free space |
| `objectPadding` | 0.25 m | Extra clearance added around obstacle polygons |
| `boundaryPadding` | 0.0 m | Inward erosion from the edges of navigation areas |
| `floorY` | 0.0 | Y coordinate placed on every path waypoint |
**Grid bounds** are computed from the union of all navigation area polygons plus a padding margin of `cellSize * 2 + agentRadius + objectPadding` on every side.
**Cell coordinate conversion:**
```
cell.x = floor((worldX - minX) / cellSize)
cell.z = floor((worldZ - minZ) / cellSize)
cellCenter.x = minX + (cell.x + 0.5) * cellSize
cellCenter.z = minZ + (cell.z + 0.5) * cellSize
```
---
## 2. Building the Walkable Grid
The grid can be loaded in two ways.
### 2a. Pre-computed grid (`.txt` file)
A plain-text file with a small header followed by rows of `1`/`0` characters:
```
cellSize 0.4
agentRadius 0.45
floorY 0.0
...
minX -5.0
minZ -5.0
gridWidth 50
gridDepth 50
11111111...
10000001...
```
This format is generated by `PathFinder::saveGrid()` after building from polygons and can be loaded much faster than recomputing from geometry.
### 2b. Polygon-based config (`.json` file)
The JSON file lists **navigation areas** (convex or concave walkable regions) and **obstacle polygons** (impassable zones within those regions):
```json
{
"cellSize": 0.4,
"areas": [
{ "name": "main_room", "available": true, "polygon": [[x,z], ...] }
],
"obstacles": [
{ "name": "table", "polygon": [[x,z], ...] }
]
}
```
**Build steps:**
1. **Mark available areas walkable** — every cell whose center lies inside any `available` navigation area polygon gets `walkable = 1`. If `boundaryPadding > 0`, cells too close to the outer edge of the area are left blocked.
2. **Mark obstacle polygons blocked** — cells whose center lies inside an obstacle polygon, or within `agentRadius + objectPadding` of its edges, are set to `0`.
Navigation areas can be toggled at runtime via `PathFinder::setAreaAvailable()`, which rebuilds the entire grid. This is used to open or close doors, gated areas, etc.
---
## 3. A\* Path Search
`PathFinder::findPath(start, end)` runs a standard A\* on the walkable grid.
**Neighbor connectivity:** 8-directional (cardinal + diagonal). Diagonal moves are blocked if either of the two adjacent cardinal cells is unwalkable (no corner-cutting).
**Step costs:** `1.0` for cardinal, `√2 ≈ 1.414` for diagonal.
**Heuristic:** Euclidean distance in cell units to the end cell.
**Start/end snapping:** If the exact cell for `start` or `end` is not walkable, `findNearestWalkableCell` expands a square ring outward (up to radius 8 m) to find the nearest walkable cell. This makes clicking slightly outside the nav mesh still produce a valid path.
**Path reconstruction:** After A\* completes, the cell chain is walked via `cameFrom[]` from `end` back to `start`, reversed, then smoothed (see §4).
**First-waypoint trimming:** If the first waypoint is within `cellSize × 0.75` of `start`, it is dropped (the character is already close enough).
**Last-waypoint precision:** If the requested `end` maps to the same cell as the snapped end cell, the last waypoint is replaced with the exact `end` world position rather than the cell centre.
---
## 4. Path Smoothing
Raw A\* paths follow the grid diagonals and produce staircase-shaped routes. A **string-pulling** (line-of-sight) pass compresses them:
```
anchor = path[0]
result = [anchor]
while anchor is not the last cell:
find the furthest cell 'next' from anchor with unobstructed line of sight
result.append(next)
anchor = next
```
Line-of-sight is checked by stepping along the segment in increments of `cellSize / 2` and verifying that each sampled cell is walkable. The result is a minimal set of waypoints connected by straight, obstacle-free segments.
---
## 5. Approaching Unreachable Destinations
When a player clicks on a point in a disconnected region (e.g., across a thin wall), the original `findPath` returns an empty path and the character does not move. This is surprising — a click on a solid wall sensibly moves the character to the nearest reachable point, but a click into an inaccessible room does nothing.
**`findPathToNearest`** fixes this with a three-step cascade:
1. Try `findPath` with dynamic obstacles (stationary characters are avoided).
2. If empty, retry `findPath` without dynamic obstacles (an NPC blocking a doorway is ignored).
3. If still empty (destination genuinely unreachable), run **nearest-reachable A\***.
**Nearest-reachable A\***, implemented in `findNearestReachableImpl`:
- Runs the identical A\* loop against the static walkable grid.
- While processing cells, tracks `bestIndex` — the already-visited cell with the smallest Euclidean distance (in cell units) to the end cell.
- If A\* exhausts all reachable space without finding `end`, it reconstructs and returns a path to `bestIndex`.
- If `bestIndex` is still the start cell (character is completely isolated), an empty path is returned and the character stays put.
The net effect: clicking anywhere in the world always moves the character as close as possible to the target, matching the behaviour of clicking on a solid wall.
`findPathToNearest` replaces the direct `findPath` call in `Location::setupNavigation`'s path planner lambda, so it applies equally to the player and all NPCs.
---
## 6. Dynamic Obstacles
When a path is planned, other characters can temporarily mark cells as blocked to make the character walk around them rather than through them.
**How it works:**
In `Location::setupNavigation`, every character is given a `PathPlanner` closure. Before calling `findPath`, the closure builds a list of `PathFinder::DynamicObstacle` entries (position + radius) representing nearby characters. `findPath` copies the static walkable grid, stamps zeros in circles around each obstacle, then runs A\* on the modified copy. The static grid is never mutated.
**Which characters become obstacles:**
A character is added as a dynamic obstacle only when **all** of these are true:
- It is not the character currently planning the path (`self`).
- It is alive and enabled.
- **It is not moving** — a moving character is transparent to pathfinding, so it does not block narrow corridors that it is actively passing through.
- Its position lies within `kDynamicObstacleInfluenceDist = 6 m` of the direct line segment from `start` to `end` (distant characters do not affect the search).
**Obstacle radius:** `character.collisionRadius × 0.6`. Using 60 % of the physical collision radius makes path planning less conservative; physical separation at full radius is still enforced by collision resolution (§8).
**Fallback when dynamic obstacles block the only path:**
If step 1 of `findPathToNearest` (with dynamic obstacles) returns empty, step 2 retries without any dynamic obstacles. This handles the common case of an NPC standing in a doorway: the player paths through the NPC's position, and the nudge logic (§8) pushes the NPC aside as the player passes.
---
## 7. Path Following
`Character::setTarget(destination, onArrived)` sets a new walk target. It calls the path planner to generate a waypoint list. The result is stored in `pathWaypoints`; the final destination is also stored in `walkTarget` and `requestedWalkTarget`.
Each frame in `Character::update`:
1. **Active target** — if `pathWaypoints` is non-empty, the character moves toward `pathWaypoints[currentWaypointIndex]`; otherwise it moves toward `walkTarget`.
2. **Movement** — the character advances along the XZ direction at `walkSpeed` m/s and rotates smoothly toward the movement direction at `rotationSpeed` rad/s.
3. **Waypoint advance** — when the character is within `WALK_THRESHOLD = 0.05 m` of the current waypoint, it advances to the next one. When the last waypoint is reached, `pathWaypoints` is cleared and the optional `onArrived` callback is fired.
4. **State machine** — the animation state switches between `STAND` and `WALK` based on whether the character is moving.
`Character::isMoving()` returns `true` if `pathWaypoints` is non-empty or the distance to `walkTarget` exceeds `WALK_THRESHOLD`. This is used by dynamic obstacle filtering and collision nudging.
**Stopping in place:** `Character::stopInPlace()` sets `walkTarget` and `requestedWalkTarget` to the current position and clears `pathWaypoints`. It is called when an external force (collision resolution) displaces a stationary player so that the player does not walk back to their previous target position.
---
## 8. Character Collision Resolution
Pathfinding alone does not prevent two characters from occupying the same space — it only steers paths around stationary characters. Physical separation is handled separately each frame by `Location::resolveCharacterCollisions`.
**Algorithm** (3 iterations per frame):
For every pair `(A, B)` of living, enabled characters:
1. Compute the overlap: `penetration = (collisionRadius_A + collisionRadius_B) - distance(A, B)`.
2. If `penetration > 0`, compute a push direction (A-to-B normal) and a push magnitude of `penetration / 2` per character.
3. Compute candidate new positions `newA` and `newB`.
4. Validate against the navigation grid (`PathFinder::isWalkable`). If a pushed position is unwalkable, only the other character is moved.
5. **Player stays put:** if the player was not moving (`!isMoving()`) before the push, `stopInPlace()` is called after the push so the player does not walk back to the old target.
6. **NPC yielding:** if one character was moving and the other was standing, `nudgeCharacterAside` is called on the standing character.
**`nudgeCharacterAside(standing, awayFrom)`:**
Gives the standing NPC a short walk target so it steps out of the way:
1. Compute the direction from `awayFrom` to the NPC's current position.
2. Try four candidate targets at distance `1.2 m` in directions: straight away, +90°, 90°, 180°.
3. Use the first candidate that is walkable (per `PathFinder::isWalkable`).
4. Call `standing->setTarget(candidate)` — the NPC takes a small step aside, then stands at the new spot.
5. The player is never nudged; combat NPCs can be nudged, but their attack AI immediately overrides the yield target on the next tick.
---
## 9. Dynamic Replanning
When characters move they can displace each other or enter each other's planned paths. `Location::updateDynamicReplans` handles this:
**Every frame:**
1. Measure how much each character moved since the last frame. Characters that moved more than `kMovedEps = 0.05 m` are collected as **movers**.
2. For each mover, find other characters that are currently walking. If the mover's position is within `kReplanTriggerDist = 1.8 m` of the segment `[walker.position → walker.nextWaypoint]`, trigger a replan for the walker via `forceReplan()`.
3. A per-character cooldown of `kReplanCooldownMs = 500 ms` prevents the same character from replanning more often than twice per second.
**`Character::forceReplan()`** re-runs the path planner from the character's current position to its stored `requestedWalkTarget`, updating `pathWaypoints` in place. If the replanned path is empty, the character stops at its current position.
The relatively generous trigger distance (1.8 m vs the old 1.1 m) and cooldown (500 ms vs 300 ms) prevent micro-jitter: small position corrections from collision resolution no longer spam replanning events.
---
## 10. Key Constants Reference
| Constant | Location | Value | Description |
|---|---|---|---|
| `cellSize` | `PathFinder` config | 0.4 m | Grid cell size |
| `agentRadius` | `PathFinder` config | 0.45 m | Character half-width for grid erosion |
| `objectPadding` | `PathFinder` config | 0.25 m | Extra clearance around obstacles |
| `WALK_THRESHOLD` | `Character.h` | 0.05 m | Distance below which a waypoint is considered reached |
| `TARGET_REPLAN_THRESHOLD` | `Character.h` | 0.25 m | Deduplication threshold in `setTarget` |
| `kDynamicObstacleInfluenceDist` | `Location.cpp` | 6.0 m | Max distance from path for a character to become an obstacle |
| `kDynamicObstacleRadiusFraction` | `Location.cpp` | 0.6× | Fraction of collision radius used for dynamic obstacle footprint |
| `kNudgeDist` | `Location.cpp` | 1.2 m | Distance an NPC steps aside when yielding |
| `kReplanTriggerDist` | `Location.cpp` | 1.8 m | Mover must be this close to a walker's path to trigger replan |
| `kReplanCooldownMs` | `Location.cpp` | 500 ms | Minimum interval between replans for any one character |
| `NPC_TALK_DISTANCE` | `Location.cpp` | 1.35 m | Distance at which walking-to-NPC interaction fires |
| `kIterations` (collision) | `Location.cpp` | 3 | Push-apart iterations per frame |

View File

@ -1,186 +0,0 @@
{
"objects": [
{
"name": "Plane",
"texturePath": "resources/w/exterior/Segmented_Plane002.png",
"meshPath": "resources/w/exterior/Segmented_Plane002.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": -5.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "door",
"texturePath": "resources/w/exterior/door002.png",
"meshPath": "resources/w/exterior/ext_door001.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": -5.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "inai",
"texturePath": "resources/w/exterior/Building_work014.png",
"meshPath": "resources/w/exterior/int_building002.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": -5.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "stairs",
"texturePath": "resources/w/exterior/Staircase001.png",
"meshPath": "resources/w/exterior/int_stairs001.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": -5.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree001",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 10.0,
"positionY": -5.0,
"positionZ": 12.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree002",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12,
"positionY": -5.0,
"positionZ": 19.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree003",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": -5.0,
"positionZ": 8.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree004",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": -5.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree005",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": -5.0,
"positionZ": -8.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree006",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 8.49915,
"positionY": -5.0,
"positionZ": -2.59884,
"scale": 1.0,
"interactive": false
},
{
"name": "tree007",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 14.5936,
"positionY": -5.0,
"positionZ": 5.3401,
"scale": 1.0,
"interactive": false
},
{
"name": "tree008",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": 23.9295,
"positionY": -5.0,
"positionZ": 9.00583,
"scale": 1.0,
"interactive": false
},
{
"name": "tree009",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 29.8128,
"positionY": -5.0,
"positionZ": -1.45278,
"scale": 1.0,
"interactive": false
},
{
"name": "tree010",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 33.1771,
"positionY": -5.0,
"positionZ": 14.609,
"scale": 1.0,
"interactive": false
}
]
}

View File

@ -1,176 +0,0 @@
{
"objects": [
{
"name": "Plane",
"texturePath": "resources/w/exterior/Segmented_Plane002.png",
"textureDarkandsPath": "resources/w/exterior/darklands_Segmented_Plane002.png",
"meshPath": "resources/w/exterior/Segmented_Plane002.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": 0.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "door",
"texturePath": "resources/w/exterior/door002.png",
"textureDarkandsPath": "resources/w/exterior/darklands_door002.png",
"meshPath": "resources/w/exterior/ext_door001.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": 0.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "inai",
"texturePath": "resources/w/exterior/Ext_Building004.png",
"textureDarkandsPath": "resources/w/exterior/darklands_Ext_Building004.png",
"meshPath": "resources/w/exterior/ext_building001.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 0.0,
"positionY": 0.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree001",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 10.0,
"positionY": 0.0,
"positionZ": 12.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree002",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12,
"positionY": 0.0,
"positionZ": 19.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree003",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": 0.0,
"positionZ": 8.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree004",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": 0.0,
"positionZ": 0.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree005",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": -12.0,
"positionY": 0.0,
"positionZ": -8.0,
"scale": 1.0,
"interactive": false
},
{
"name": "tree006",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 8.49915,
"positionY": 0.0,
"positionZ": -2.59884,
"scale": 1.0,
"interactive": false
},
{
"name": "tree007",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 14.5936,
"positionY": 0.0,
"positionZ": 5.3401,
"scale": 1.0,
"interactive": false
},
{
"name": "tree008",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 1.5707963267948966,
"rotationZ": 0.0,
"positionX": 23.9295,
"positionY": 0.0,
"positionZ": 9.00583,
"scale": 1.0,
"interactive": false
},
{
"name": "tree009",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": 0.0,
"rotationZ": 0.0,
"positionX": 29.8128,
"positionY": 0.0,
"positionZ": -1.45278,
"scale": 1.0,
"interactive": false
},
{
"name": "tree010",
"texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0,
"rotationY": -1.5707963267948966,
"rotationZ": 0.0,
"positionX": 33.1771,
"positionY": 0.0,
"positionZ": 14.609,
"scale": 1.0,
"interactive": false
}
]
}

View File

@ -47,7 +47,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 10.0, "positionX": 10.0,
"positionY": 0.0, "positionY": 0.0,
@ -60,7 +60,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12, "positionX": -12,
"positionY": 0.0, "positionY": 0.0,
@ -86,7 +86,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12.0, "positionX": -12.0,
"positionY": 0.0, "positionY": 0.0,
@ -99,7 +99,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12.0, "positionX": -12.0,
"positionY": 0.0, "positionY": 0.0,
@ -125,7 +125,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 14.5936, "positionX": 14.5936,
"positionY": 0.0, "positionY": 0.0,
@ -138,7 +138,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 23.9295, "positionX": 23.9295,
"positionY": 0.0, "positionY": 0.0,
@ -164,7 +164,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 33.1771, "positionX": 33.1771,
"positionY": 0.0, "positionY": 0.0,

View File

@ -105,7 +105,7 @@
"texturePath": "resources/w/interior/doors_tex001.png", "texturePath": "resources/w/interior/doors_tex001.png",
"meshPath": "resources/w/interior/outputRoom_N_0_Leaf001.txt", "meshPath": "resources/w/interior/outputRoom_N_0_Leaf001.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5708, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 1.5, "positionX": 1.5,
"positionY": 0.975, "positionY": 0.975,
@ -117,7 +117,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 10.0, "positionX": 10.0,
"positionY": -5.0, "positionY": -5.0,
@ -129,7 +129,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12, "positionX": -12,
"positionY": -5.0, "positionY": -5.0,
@ -153,7 +153,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12.0, "positionX": -12.0,
"positionY": -5.0, "positionY": -5.0,
@ -165,7 +165,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": -12.0, "positionX": -12.0,
"positionY": -5.0, "positionY": -5.0,
@ -189,7 +189,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 14.5936, "positionX": 14.5936,
"positionY": -5.0, "positionY": -5.0,
@ -201,7 +201,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 1.5707963267948966, "rotationY": 90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 23.9295, "positionX": 23.9295,
"positionY": -5.0, "positionY": -5.0,
@ -225,7 +225,7 @@
"texturePath": "resources/w/exterior/tree001.png", "texturePath": "resources/w/exterior/tree001.png",
"meshPath": "resources/w/exterior/tree003.txt", "meshPath": "resources/w/exterior/tree003.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": -1.5707963267948966, "rotationY": -90,
"rotationZ": 0.0, "rotationZ": 0.0,
"positionX": 33.1771, "positionX": 33.1771,
"positionY": -5.0, "positionY": -5.0,

View File

@ -30,8 +30,8 @@
}, },
{ {
"name": "Computer001", "name": "Computer001",
"texturePath": "resources/w/interior/computer_texture001.png", "texturePath": "resources/w/interior/computer_texture002.png",
"meshPath": "resources/w/interior/computer001_003.txt", "meshPath": "resources/w/interior/computer001_004.txt",
"rotationX": 0.0, "rotationX": 0.0,
"rotationY": 0.0, "rotationY": 0.0,
"rotationZ": 0.0, "rotationZ": 0.0,

View File

@ -1,99 +0,0 @@
{
"npcs": [
{
"id": "npc_04_ghost",
"name": "Беспокойный Призрак",
"texturePath": "resources/w/ghost_skin001.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"positionX": -0.28594,
"positionY": 0,
"positionZ": 13.9641,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 1.0,
"gift": {
"id": "ghost_essence",
"name": "Ghost's Essence",
"description": "A mysterious essence from the Ghost realm",
"icon": "resources/w/red.png"
}
},
{
"id": "ghost_01x",
"name": "Опасный Призрак",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": 4.09561,
"positionY": 0.0,
"positionZ": 6.28458,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 35,
"canAttack": true
},
{
"id": "ghost_02x",
"name": "Злой призрак",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": -4.95651,
"positionY": 0.0,
"positionZ": 5.81422,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 25,
"canAttack": true
},
{
"id": "ghost_02x",
"name": "Злой дух",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": 0,
"positionY": 0.0,
"positionZ": 10,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 15,
"canAttack": true
}
]
}

View File

@ -1,125 +0,0 @@
{
"npcs": [
{
"id": "npc_01_default",
"name": "Студент",
"animationIdlePath": "resources/w/jam/man_stand_idle002.anim",
"animationWalkPath": "resources/w/jam/man_walk002.anim",
"meshTextures": {
"Body": "resources/w/jam/male_packed0_diffuse.png",
"Bottoms": "resources/w/jam/male_packed1_diffuse.png",
"Eyelashes": "resources/w/jam/male_packed0_diffuse.png",
"Eyes": "resources/w/jam/male_packed0_diffuse.png",
"Eyewear": "resources/w/jam/male_packed0_diffuse.png",
"Gloves": "resources/w/jam/male_packed1_diffuse.png",
"Hair": "resources/w/jam/male_packed0_diffuse.png",
"Shoes": "resources/w/jam/male_packed1_diffuse.png",
"Tops": "resources/w/jam/male_packed2_diffuse.png"
},
"positionX": 2.95,
"positionY": 0.0,
"positionZ": 16.65,
"facingAngle" : 3.141592,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.0001,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0,
"gift": {
"id": "guard_token",
"name": "Guard's Token",
"description": "A token from the Guard - sign of respect",
"icon": "resources/w/red.png"
}
},
{
"id": "npc_02_woman",
"name": "Студентка",
"animationIdlePath": "resources/w/girl/girl_walk001.txt",
"animationWalkPath": "resources/w/girl/girl_walk001.txt",
"meshTextures": {
"polySurface1": "resources/w/girl/Chat_02_diff_1.png"
},
"positionX": 19.5,
"positionY": 0.0,
"positionZ": 32.0,
"facingAngle" : 3.141592,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0
},
{
"id": "npc_03_salesman",
"name": "Мухтар Байке",
"animationIdlePath": "resources/w/jam/salesperson_stand_idle003.anim",
"animationWalkPath": "resources/w/jam/salesperson_walk001.anim",
"meshTextures": {
"Body": "resources/w/jam/Salesperson_packed0_diffuse.png",
"Bottoms": "resources/w/jam/Salesperson_packed2_diffuse.png",
"Eyelashes": "resources/w/jam/Salesperson_packed0_diffuse.png",
"Eyes": "resources/w/jam/Salesperson_packed0_diffuse.png",
"Hats": "resources/w/jam/Salesperson_packed1_diffuse.png",
"Mustashes": "resources/w/jam/Salesperson_packed1_diffuse.png",
"Shoes": "resources/w/jam/Salesperson_packed1_diffuse.png",
"Tops": "resources/w/jam/Salesperson_packed1_diffuse.png"
},
"positionX": -1.94774,
"positionY": 0.0,
"positionZ": 16.2712,
"facingAngle" : 3.141592,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.001,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0
},
{
"id": "npc_teacher",
"name": "Аида Токтоналы",
"animationIdlePath": "resources/w/new_anims/teacher_stand_idle001.txt",
"animationWalkPath": "resources/w/new_anims/woman_walk4_001.txt",
"meshTextures": {
"Teacher_lowpoly": "resources/w/new_anims/UniV_Grid_2K.001_Base_color.png"
},
"positionX": -3.76765,
"positionY": 0.0,
"positionZ": -21.8495,
"facingAngle" : 0,
"walkSpeed": 1.3,
"rotationSpeed": 8.0,
"modelScale": 1.0,
"modelCorrectionRotX": -90.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0
},
{
"id": "npc_teacher02",
"name": "Асель Дженибековна",
"animationIdlePath": "resources/w/new_anims/teacher_stand_idle001.txt",
"animationWalkPath": "resources/w/new_anims/woman_walk4_001.txt",
"meshTextures": {
"Teacher_lowpoly": "resources/w/new_anims/UniV_Grid_2K.001_Base_color.png"
},
"positionX": 12.7499,
"positionY": 0.0,
"positionZ": -20.8708,
"facingAngle" : 0,
"walkSpeed": 1.3,
"rotationSpeed": 8.0,
"modelScale": 1.0,
"modelCorrectionRotX": -90.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0
}
]
}

View File

@ -3,19 +3,19 @@
{ {
"id": "npc_01_woman", "id": "npc_01_woman",
"name": "Бермет", "name": "Бермет",
"animationIdlePath": "resources/w/girl/girl_idle001.txt", "animationIdlePath": "resources/w/girlfriend/girlfriend_idle003.txt",
"animationWalkPath": "resources/w/girl/girl_walk010.txt", "animationWalkPath": "resources/w/girlfriend/girlfriend_walk003.txt",
"meshTextures": { "meshTextures": {
"polySurface1": "resources/w/girl/Chat_02_diff_1r006.png" "Girl_Low": "resources/w/girlfriend/Girl_Base_color.png"
}, },
"positionX": 1.03298, "positionX": 1.03298,
"positionY": 0.0, "positionY": 0.0,
"positionZ": -4.61801, "positionZ": -4.61801,
"facingAngle" : 3.141592, "facingAngle" : 180,
"walkSpeed": 1.8, "walkSpeed": 1.8,
"rotationSpeed": 8.0, "rotationSpeed": 8.0,
"modelScale": 0.016, "modelScale": 1.0,
"modelCorrectionRotX": 0.0, "modelCorrectionRotX": -90.0,
"modelCorrectionRotY": 180.0, "modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0, "modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0 "interactionRadius": 2.0
@ -59,7 +59,7 @@
"positionX": -6.33478, "positionX": -6.33478,
"positionY": 0.0, "positionY": 0.0,
"positionZ": -15.0382, "positionZ": -15.0382,
"facingAngle" : 3.141592, "facingAngle" : 180,
"walkSpeed": 1.5, "walkSpeed": 1.5,
"rotationSpeed": 8.0, "rotationSpeed": 8.0,
"modelScale": 0.0001, "modelScale": 0.0001,

View File

@ -14,7 +14,7 @@
"walkSpeed": 1.3, "walkSpeed": 1.3,
"rotationSpeed": 8.0, "rotationSpeed": 8.0,
"modelScale": 1.0, "modelScale": 1.0,
"facingAngle" : 3.141592, "facingAngle" : 180,
"modelCorrectionRotX": -90.0, "modelCorrectionRotX": -90.0,
"modelCorrectionRotY": 180.0, "modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0, "modelCorrectionRotZ": 0.0,
@ -23,19 +23,19 @@
{ {
"id": "npc_02_woman", "id": "npc_02_woman",
"name": "Айпери", "name": "Айпери",
"animationIdlePath": "resources/w/girl/girl_idle001.txt", "animationIdlePath": "resources/w/girlfriend/girlfriend_idle003.txt",
"animationWalkPath": "resources/w/girl/girl_walk002.txt", "animationWalkPath": "resources/w/girlfriend/girlfriend_walk003.txt",
"meshTextures": { "meshTextures": {
"polySurface1": "resources/w/girl/Chat_02_diff_1r006.png" "Girl_Low": "resources/w/girlfriend/Girl_Base_color.png"
}, },
"positionX": 0.764049, "positionX": 0.764049,
"positionY": 0.0, "positionY": 0.0,
"positionZ": -8.53475, "positionZ": -8.53475,
"facingAngle" : 3.141592, "facingAngle" : 180,
"walkSpeed": 1.66, "walkSpeed": 1.66,
"rotationSpeed": 8.0, "rotationSpeed": 8.0,
"modelScale": 0.016, "modelScale": 1.0,
"modelCorrectionRotX": 0.0, "modelCorrectionRotX": -90.0,
"modelCorrectionRotY": 180.0, "modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0, "modelCorrectionRotZ": 0.0,
"interactionRadius": 2.0, "interactionRadius": 2.0,

View File

@ -1,30 +0,0 @@
{
"teleports": [
{
"id": "tp_loc2_to_loc1",
"positionX": 8.2,
"positionY": 0.0,
"positionZ": -9.9,
"radius": 1.5,
"active": true,
"destinationLocation": "uni_interior",
"destinationPositionX": 2.64621,
"destinationPositionY": 0.0,
"destinationPositionZ": -9.37259,
"destinationRotationY": -1.5708
},
{
"id": "tp_loc2_to_dorm",
"positionX": -21.7327,
"positionY": 0.0,
"positionZ": -34.1036,
"radius": 2.5,
"active": true,
"destinationLocation": "location_dorm",
"destinationPositionX": -8.32343,
"destinationPositionY": 0.0,
"destinationPositionZ": -0.152264,
"destinationRotationY": 1.5708
}
]
}

View File

@ -1,4 +0,0 @@
{
"trigger_zones": [
]
}

View File

@ -34,7 +34,7 @@
"positionZ": 6.17516, "positionZ": 6.17516,
"radius": 2.0, "radius": 2.0,
"hysteresis": 0.1, "hysteresis": 0.1,
"enabled": false "enabled": true
} }
] ]
} }

View File

@ -833,6 +833,132 @@
"type": "End" "type": "End"
} }
] ]
},
{
"id": "book_dialog003",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Да, вот эта книга! Я возьму ее.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "book_dialog004",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Я вернул книгу на место.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "book_dialog005",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "На этой полке лежат самые скучные книги в этом кабинете.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "book_dialog006",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Я надеюсь, мне эта книга больше не пригодится.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "computer_dialog001",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Это старый библиотечный компьютер, он даже не подключен к интернету.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "computer_dialog002",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "От этого компьютера сейчас не будет никакого толку.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "computer_dialog003",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Надеюсь мне больше не придется притрагиваться к этому компьютеру.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
}, },
{ {
"id": "dialog_report_card001", "id": "dialog_report_card001",
@ -936,7 +1062,9 @@
}, },
{ {
"id": "test_cutscene_02", "id": "test_cutscene_02",
"background": "resources/test_cutscene001.png", "background": "resources/black.png",
"backgroundWidth" : 1280,
"backgroundHeight" : 720,
"durationMs": 5000, "durationMs": 5000,
"fadeOutMs": 500, "fadeOutMs": 500,
"fadeInMs": 500, "fadeInMs": 500,
@ -980,7 +1108,9 @@
}, },
{ {
"id": "darklands_exit001", "id": "darklands_exit001",
"background": "resources/test_cutscene001.png", "background": "resources/black.png",
"backgroundWidth" : 1280,
"backgroundHeight" : 720,
"durationMs": 5000, "durationMs": 5000,
"fadeOutMs": 500, "fadeOutMs": 500,
"fadeInMs": 500, "fadeInMs": 500,
@ -1002,21 +1132,23 @@
], ],
"lines": [ "lines": [
{ {
"speaker": "Бекзат",
"portrait": "resources/hero.png", "portrait": "resources/hero.png",
"text": "Кажется, после того как я умер, я очнулся.", "text": "Мгновенно как я упал без сил, что-то сверкнуло.",
"durationMs": 3000 "durationMs": 3000
}, },
{ {
"speaker": "Бекзат",
"portrait": "resources/hero.png", "portrait": "resources/hero.png",
"text": "Жесть вообще, хочу спать.", "text": "Я открыл глаза и понял, что я по-прежнему в универе.",
"durationMs": 3000 "durationMs": 3000
}, },
{ {
"speaker": "Бекзат",
"portrait": "resources/hero.png", "portrait": "resources/hero.png",
"text": "Который час?", "text": "Все тело болело, как будто я всю ночь таскал мешки с цементом.",
"durationMs": 3000
},
{
"portrait": "resources/hero.png",
"text": "А еще мне сильно хотелось спать...",
"durationMs": 2000, "durationMs": 2000,
"background": "resources/test_cutscene001.png" "background": "resources/test_cutscene001.png"
} }

BIN
resources/first_cutscene.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/portraits/elder_bor_neutral.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/second_cutscene.png (Stored with Git LFS)

Binary file not shown.

View File

@ -84,21 +84,35 @@ game_api.npc_walk_to(0, -4.57412, 0, 6.78495, on_teacher_arrived2)
end end
function on_book_pickup() function on_book_pickup()
print("on_book_pickup")
local day = game_api.getIntValue("day")
if (teacher_told_about_book) then if (teacher_told_about_book) then
print("on_book_pickup step1")
if not player_hold_book then if not player_hold_book then
if (night_time) or (day >=1) then
game_api.start_dialogue("book_dialog006")
else
game_api.pickup_item("book") game_api.pickup_item("book")
game_api.deactivate_interactive_object("Book001") game_api.deactivate_interactive_object("Book001")
player_hold_book = true player_hold_book = true
game_api.start_dialogue("book_dialog003")
--game_api.set_trigger_zone_enabled(3, true)
end
else else
game_api.remove_item("book") game_api.remove_item("book")
game_api.activate_interactive_object("Book001") game_api.activate_interactive_object("Book001")
player_hold_book = false player_hold_book = false
game_api.start_dialogue("book_dialog004")
--game_api.set_trigger_zone_enabled(3, false)
end end
else
print("on_book_pickup step2")
game_api.start_dialogue("book_dialog005")
end end
end end
function on_bookshelf_clicked() function on_bookshelf_clicked()
if not player_hold_book then --[[if not player_hold_book then
game_api.pickup_item("book") game_api.pickup_item("book")
game_api.deactivate_interactive_object("Book001") game_api.deactivate_interactive_object("Book001")
player_hold_book = true player_hold_book = true
@ -108,7 +122,8 @@ function on_bookshelf_clicked()
game_api.activate_interactive_object("Book001") game_api.activate_interactive_object("Book001")
player_hold_book = false player_hold_book = false
game_api.set_trigger_zone_enabled(3, false) game_api.set_trigger_zone_enabled(3, false)
end end]]
on_book_pickup()
end end
@ -353,11 +368,16 @@ print("Teacher arrived2")
end end
function book_dialog_zone001_enter_callback() function book_dialog_zone001_enter_callback()
print("book_dialog_zone001_enter_callback step 1")
if (game_api.is_darklands()) then if (game_api.is_darklands()) then
print("book_dialog_zone001_enter_callback step 2")
game_api.start_dialogue("ghost_dialog002") game_api.start_dialogue("ghost_dialog002")
game_api.set_trigger_zone_enabled(3, false) game_api.set_trigger_zone_enabled(3, false)
else else
if (night_time == false) then
print("book_dialog_zone001_enter_callback step 3")
if (player_hold_book) and (night_time == false) then
print("book_dialog_zone001_enter_callback step 4")
game_api.start_dialogue("book_dialog001") game_api.start_dialogue("book_dialog001")
game_api.switch_navigation(5) game_api.switch_navigation(5)
end end
@ -365,7 +385,8 @@ function book_dialog_zone001_enter_callback()
end end
function book_dialog_zone001_exit_callback() function book_dialog_zone001_exit_callback()
if (night_time == false) then print("book_dialog_zone001_exit_callback step 1")
if (player_hold_book) and (night_time == false) then
game_api.switch_navigation(3) game_api.switch_navigation(3)
end end
end end
@ -376,14 +397,27 @@ print("on_computer_clicked--")
if (day == 1) then if (day == 1) then
-- TODO: some dialog like I don't need it -- TODO: some dialog like I don't need it
print("on_computer_clicked--1")
game_api.start_dialogue("computer_dialog003")
else else
if teacher_told_about_book then
print("on_computer_clicked--2")
if (night_time == false) then if (night_time == false) then
print("on_computer_clicked--3")
if (player_hold_book) then if (player_hold_book) then
print("on_computer_clicked--4")
game_api.start_cutscene("test_cutscene_02") game_api.start_cutscene("test_cutscene_02")
else else
print("on_computer_clicked--5")
game_api.start_dialogue("book_dialog002") game_api.start_dialogue("book_dialog002")
end end
else
print("on_computer_clicked--6")
game_api.start_dialogue("computer_dialog002")
end
else
print("on_computer_clicked--7")
game_api.start_dialogue("computer_dialog001")
end end
end end
end end
@ -469,6 +503,8 @@ game_api.set_darklands_callbacks(
first_time_darklands = false first_time_darklands = false
end end
game_api.set_trigger_zone_enabled(1, false)
game_api.set_npc_enabled(0, false) game_api.set_npc_enabled(0, false)
game_api.set_npc_enabled(1, false) game_api.set_npc_enabled(1, false)

BIN
resources/w/girlfriend/Girl_Base_color.png (Stored with Git LFS) Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,182 +0,0 @@
===Vertices (Split by UV/Normal): 119
V 0: Pos(-0.321174, -0.380694, 0.03328) Norm(-0.577341, -0.577355, 0.577355) UV(0.622728, 0.985786)
V 1: Pos(-0.321174, -0.080976, -0.12644) Norm(-0.577341, 0.577355, -0.577355) UV(0.701365, 0.838077)
V 2: Pos(-0.321174, -0.380694, -0.12644) Norm(-0.57735, -0.577325, -0.577377) UV(0.701365, 0.985786)
V 3: Pos(-0.321174, -0.080976, 0.03328) Norm(-0.57735, 0.577325, 0.577377) UV(0.84347, 0.785241)
V 4: Pos(0.038545, -0.080976, -0.12644) Norm(0.57735, 0.577325, -0.577377) UV(0.764763, 0.962461)
V 5: Pos(-0.321174, -0.080976, -0.12644) Norm(-0.577341, 0.577355, -0.577355) UV(0.764763, 0.785276)
V 6: Pos(0.038545, -0.080976, 0.03328) Norm(0.577341, 0.577355, 0.577355) UV(-0.0, 0.985999)
V 7: Pos(0.038545, -0.380694, -0.12644) Norm(0.577341, -0.577355, -0.577355) UV(0.078326, 0.83829)
V 8: Pos(0.038545, -0.080976, -0.12644) Norm(0.57735, 0.577325, -0.577377) UV(0.078326, 0.985999)
V 9: Pos(0.038545, -0.380694, 0.03328) Norm(0.57735, -0.577325, 0.577377) UV(0.843469, 0.962426)
V 10: Pos(-0.321174, -0.380694, -0.12644) Norm(-0.57735, -0.577325, -0.577377) UV(0.922175, 0.785276)
V 11: Pos(0.038545, -0.380694, -0.12644) Norm(0.577341, -0.577355, -0.577355) UV(0.922175, 0.962461)
V 12: Pos(0.038545, -0.080976, -0.12644) Norm(0.57735, 0.577325, -0.577377) UV(0.717688, 0.0)
V 13: Pos(-0.321174, -0.380694, -0.12644) Norm(-0.57735, -0.577325, -0.577377) UV(0.865397, 0.17727)
V 14: Pos(-0.321174, -0.080976, -0.12644) Norm(-0.577341, 0.577355, -0.577355) UV(0.717688, 0.17727)
V 15: Pos(-0.321174, -0.080976, 0.03328) Norm(-0.57735, 0.577325, 0.577377) UV(0.764763, 0.60813)
V 16: Pos(0.038545, -0.380694, 0.03328) Norm(0.57735, -0.577325, 0.577377) UV(0.912472, 0.785241)
V 17: Pos(0.038545, -0.080976, 0.03328) Norm(0.577341, 0.577355, 0.577355) UV(0.764763, 0.785241)
V 18: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.865397, 0.0)
V 19: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.973174, 0.206141)
V 20: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.865397, 0.206141)
V 21: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.206323, 0.553153)
V 22: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.0, 0.83829)
V 23: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.0, 0.553246)
V 24: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.867943, 0.277359)
V 25: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.97572, 0.482684)
V 26: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.867943, 0.482684)
V 27: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.206322, 0.838197)
V 28: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.412645, 0.553246)
V 29: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.412645, 0.83829)
V 30: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.514952, 0.553153)
V 31: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.622728, 0.838334)
V 32: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.514952, 0.838334)
V 33: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.622728, 0.553153)
V 34: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.730505, 0.838077)
V 35: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.622728, 0.838077)
V 36: Pos(-0.04492, -0.422368, 0.425114) Norm(-0.964019, -0.126929, -0.233572) UV(0.551589, 0.17729)
V 37: Pos(0.154366, -0.062626, -0.106573) Norm(0.092451, 0.354611, -0.930432) UV(0.717688, 0.0)
V 38: Pos(0.154366, -0.422368, -0.106573) Norm(0.09503, -0.342725, -0.934617) UV(0.717688, 0.17729)
V 39: Pos(-0.04492, -0.062626, 0.425114) Norm(-0.95805, 0.148128, -0.245352) UV(0.049252, 0.277359)
V 40: Pos(0.16953, 0.037313, 0.020093) Norm(0.364582, 0.91558, -0.169683) UV(0.275794, -0.0)
V 41: Pos(0.154366, -0.062626, -0.106573) Norm(0.092451, 0.354611, -0.930432) UV(0.226542, 0.277359)
V 42: Pos(-0.049131, 0.037313, 0.535225) Norm(-0.186957, 0.70094, 0.68828) UV(0.27372, 0.553153)
V 43: Pos(0.16953, -0.522306, 0.020093) Norm(0.391468, -0.900748, -0.188163) UV(0.537026, 0.277359)
V 44: Pos(0.16953, 0.037313, 0.020093) Norm(0.364582, 0.91558, -0.169683) UV(0.537026, 0.553153)
V 45: Pos(-0.049131, -0.522306, 0.535225) Norm(-0.192057, -0.680692, 0.706946) UV(0.551589, -0.0)
V 46: Pos(0.154366, -0.422368, -0.106573) Norm(0.09503, -0.342725, -0.934617) UV(0.325047, 0.277359)
V 47: Pos(0.16953, -0.522306, 0.020093) Norm(0.391468, -0.900748, -0.188163) UV(0.275794, -0.0)
V 48: Pos(0.16953, 0.037313, 0.020093) Norm(0.364582, 0.91558, -0.169683) UV(-0.0, 0.553153)
V 49: Pos(0.154366, -0.422368, -0.106573) Norm(0.09503, -0.342725, -0.934617) UV(0.27372, 0.326611)
V 50: Pos(0.154366, -0.062626, -0.106573) Norm(0.092451, 0.354611, -0.930432) UV(0.27372, 0.503901)
V 51: Pos(-0.04492, -0.062626, 0.425114) Norm(-0.95805, 0.148128, -0.245352) UV(0.537025, 0.503901)
V 52: Pos(-0.049131, -0.522306, 0.535225) Norm(-0.192057, -0.680692, 0.706946) UV(0.764763, 0.277359)
V 53: Pos(-0.049131, 0.037313, 0.535225) Norm(-0.186957, 0.70094, 0.68828) UV(0.764763, 0.553154)
V 54: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.730505, 0.883925)
V 55: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.755124, 0.553154)
V 56: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.755124, 0.883925)
V 57: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.893159, 0.482684)
V 58: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(0.867943, 0.586724)
V 59: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.867943, 0.482698)
V 60: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.97572, 0.60813)
V 61: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(1.0, 0.277359)
V 62: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(1.0, 0.60813)
V 63: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(0.918374, 0.586724)
V 64: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.893159, 0.482684)
V 65: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.918374, 0.482698)
V 66: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(0.412645, 0.883924)
V 67: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.514952, 0.553153)
V 68: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.514952, 0.883924)
V 69: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.764763, 0.60813)
V 70: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.867943, 0.277359)
V 71: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.867943, 0.60813)
V 72: Pos(0.243142, 0.221328, -0.083115) Norm(-0.577358, -0.577347, 0.577347) UV(0.551589, 0.260175)
V 73: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.573298, 0.228587)
V 74: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.573298, 0.260175)
V 75: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.658221, 0.17729)
V 76: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.636492, 0.228573)
V 77: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.636492, 0.177299)
V 78: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.573298, 0.260175)
V 79: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.594921, 0.228588)
V 80: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.594921, 0.260175)
V 81: Pos(0.347237, 0.221328, -0.083115) Norm(0.577359, -0.577332, 0.577359) UV(0.614763, 0.228563)
V 82: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.636492, 0.177299)
V 83: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.636492, 0.228573)
V 84: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.551589, 0.17729)
V 85: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.583176, 0.228588)
V 86: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.551589, 0.228588)
V 87: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.583176, 0.17729)
V 88: Pos(0.347237, 0.221328, -0.083115) Norm(0.577359, -0.577332, 0.577359) UV(0.614763, 0.228541)
V 89: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.583176, 0.228541)
V 90: Pos(-0.321174, -0.080976, 0.03328) Norm(-0.57735, 0.577325, 0.577377) UV(0.622728, 0.838077)
V 91: Pos(0.038545, -0.080976, 0.03328) Norm(0.577341, 0.577355, 0.577355) UV(0.84347, 0.962426)
V 92: Pos(0.038545, -0.380694, 0.03328) Norm(0.57735, -0.577325, 0.577377) UV(-0.0, 0.83829)
V 93: Pos(-0.321174, -0.380694, 0.03328) Norm(-0.577341, -0.577355, 0.577355) UV(0.843469, 0.785241)
V 94: Pos(0.038545, -0.380694, -0.12644) Norm(0.577341, -0.577355, -0.577355) UV(0.865397, 0.0)
V 95: Pos(-0.321174, -0.380694, 0.03328) Norm(-0.577341, -0.577355, 0.577355) UV(0.912472, 0.60813)
V 96: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.973174, 0.0)
V 97: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.206323, 0.838198)
V 98: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.97572, 0.277359)
V 99: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.206322, 0.553153)
V 100: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.622728, 0.553153)
V 101: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.730505, 0.553153)
V 102: Pos(-0.04492, -0.062626, 0.425114) Norm(-0.95805, 0.148128, -0.245352) UV(0.551589, 0.0)
V 103: Pos(-0.049131, 0.037313, 0.535225) Norm(-0.186957, 0.70094, 0.68828) UV(-0.0, -0.0)
V 104: Pos(-0.049131, -0.522306, 0.535225) Norm(-0.192057, -0.680692, 0.706946) UV(0.27372, 0.277359)
V 105: Pos(-0.04492, -0.422368, 0.425114) Norm(-0.964019, -0.126929, -0.233572) UV(0.502336, 0.277359)
V 106: Pos(0.16953, -0.522306, 0.020093) Norm(0.391468, -0.900748, -0.188163) UV(-0.0, 0.277359)
V 107: Pos(-0.04492, -0.422368, 0.425114) Norm(-0.964019, -0.126929, -0.233572) UV(0.537025, 0.326611)
V 108: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.730505, 0.553154)
V 109: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.893159, 0.58671)
V 110: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.97572, 0.277359)
V 111: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.893159, 0.58671)
V 112: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(0.412645, 0.553153)
V 113: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.764763, 0.277359)
V 114: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.551589, 0.228587)
V 115: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.658221, 0.228563)
V 116: Pos(0.347237, 0.221328, -0.083115) Norm(0.577359, -0.577332, 0.577359) UV(0.573298, 0.228588)
V 117: Pos(0.243142, 0.221328, -0.083115) Norm(-0.577358, -0.577347, 0.577347) UV(0.614763, 0.17729)
V 118: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.583176, 0.17729)
===Triangles (Indices): 60
Tri: 0 1 2
Tri: 3 4 5
Tri: 6 7 8
Tri: 9 10 11
Tri: 12 13 14
Tri: 15 16 17
Tri: 18 19 20
Tri: 21 22 23
Tri: 24 25 26
Tri: 27 28 29
Tri: 30 31 32
Tri: 33 34 35
Tri: 36 37 38
Tri: 39 40 41
Tri: 42 43 44
Tri: 45 46 47
Tri: 48 49 50
Tri: 51 52 53
Tri: 54 55 56
Tri: 57 58 59
Tri: 60 61 62
Tri: 63 64 65
Tri: 66 67 68
Tri: 69 70 71
Tri: 72 73 74
Tri: 75 76 77
Tri: 78 79 80
Tri: 81 82 83
Tri: 84 85 86
Tri: 87 88 89
Tri: 0 90 1
Tri: 3 91 4
Tri: 6 92 7
Tri: 9 93 10
Tri: 12 94 13
Tri: 15 95 16
Tri: 18 96 19
Tri: 21 97 22
Tri: 24 98 25
Tri: 27 99 28
Tri: 30 100 31
Tri: 33 101 34
Tri: 36 102 37
Tri: 39 103 40
Tri: 42 104 43
Tri: 45 105 46
Tri: 48 106 49
Tri: 51 107 52
Tri: 54 108 55
Tri: 57 109 58
Tri: 60 110 61
Tri: 63 111 64
Tri: 66 112 67
Tri: 69 113 70
Tri: 72 114 73
Tri: 75 115 76
Tri: 78 116 79
Tri: 81 117 82
Tri: 84 118 85
Tri: 87 117 88

View File

@ -0,0 +1,172 @@
===Vertices (Split by UV/Normal): 109
V 0: Pos(0.02036, -0.316218, 0.03328) Norm(-0.577372, -0.57734, 0.57734) UV(0.582523, 0.71522)
V 1: Pos(0.02036, -0.14665, -0.12644) Norm(-0.577372, 0.57734, -0.57734) UV(0.672618, 0.618748)
V 2: Pos(0.02036, -0.316218, -0.12644) Norm(-0.577341, -0.577361, -0.577348) UV(0.672618, 0.715234)
V 3: Pos(0.02036, -0.14665, 0.03328) Norm(-0.577341, 0.577354, 0.577356) UV(0.0, 0.850964)
V 4: Pos(0.132668, -0.14665, -0.12644) Norm(0.543117, 0.593412, -0.594042) UV(0.063904, 0.941846)
V 5: Pos(0.02036, -0.14665, -0.12644) Norm(-0.577372, 0.57734, -0.57734) UV(0.0, 0.941846)
V 6: Pos(0.14154, -0.14665, 0.03328) Norm(0.584307, 0.579838, 0.567779) UV(0.672958, 0.522251)
V 7: Pos(0.14154, -0.316218, -0.12644) Norm(0.583322, -0.568894, -0.579737) UV(0.582523, 0.618734)
V 8: Pos(0.132668, -0.14665, -0.12644) Norm(0.543117, 0.593412, -0.594042) UV(0.582929, 0.522217)
V 9: Pos(0.14154, -0.316218, 0.03328) Norm(0.577339, -0.577359, 0.577353) UV(0.068952, 0.850964)
V 10: Pos(0.02036, -0.316218, -0.12644) Norm(-0.577341, -0.577361, -0.577348) UV(0.137905, 0.941846)
V 11: Pos(0.14154, -0.316218, -0.12644) Norm(0.583322, -0.568894, -0.579737) UV(0.068952, 0.941846)
V 12: Pos(0.132668, -0.14665, -0.12644) Norm(0.543117, 0.593412, -0.594042) UV(0.646367, 0.811719)
V 13: Pos(0.02036, -0.316218, -0.12644) Norm(-0.577341, -0.577361, -0.577348) UV(0.582523, 0.908205)
V 14: Pos(0.02036, -0.14665, -0.12644) Norm(-0.577372, 0.57734, -0.57734) UV(0.582523, 0.811719)
V 15: Pos(0.02036, -0.14665, 0.03328) Norm(-0.577341, 0.577354, 0.577356) UV(0.582523, 0.811719)
V 16: Pos(0.14154, -0.316218, 0.03328) Norm(0.577339, -0.577359, 0.577353) UV(0.65141, 0.715234)
V 17: Pos(0.14154, -0.14665, 0.03328) Norm(0.584307, 0.579838, 0.567779) UV(0.65141, 0.811719)
V 18: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.680198, 0.763823)
V 19: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.804635, 0.999981)
V 20: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.680198, 1.0)
V 21: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.0, 0.850964)
V 22: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.238239, 0.521684)
V 23: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.238239, 0.850964)
V 24: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.804635, 0.658012)
V 25: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.929069, 0.895159)
V 26: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.804635, 0.895235)
V 27: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.344284, 0.851497)
V 28: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.582523, 0.522217)
V 29: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.582523, 0.851497)
V 30: Pos(-0.0362, 0.561483, -0.134268) Norm(0.577355, 0.577371, -0.577325) UV(0.929072, 0.657935)
V 31: Pos(-0.614892, 0.342792, -0.134268) Norm(-0.577355, -0.577371, -0.577325) UV(0.804635, 0.328968)
V 32: Pos(-0.614892, 0.561483, -0.134268) Norm(-0.57734, 0.577355, -0.577355) UV(0.929072, 0.328968)
V 33: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.804635, 0.0)
V 34: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.929072, 0.328968)
V 35: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.804635, 0.328968)
V 36: Pos(-0.04492, -0.422368, 0.425114) Norm(-0.970839, -0.041701, -0.236076) UV(0.062426, 0.358244)
V 37: Pos(0.154366, -0.062626, -0.04817) Norm(0.071923, 0.11404, -0.990869) UV(0.344284, 0.15346)
V 38: Pos(0.154366, -0.422368, -0.04817) Norm(0.073067, -0.106497, -0.991625) UV(0.344284, 0.358155)
V 39: Pos(-0.04492, -0.062626, 0.425114) Norm(-0.970251, 0.045613, -0.237765) UV(0.062426, 0.153549)
V 40: Pos(0.16953, 0.207044, 0.020093) Norm(0.24987, 0.873883, -0.417005) UV(0.306911, -0.0)
V 41: Pos(-0.049131, 0.207044, 0.535225) Norm(-0.195195, 0.782445, 0.591336) UV(0.344284, 0.522217)
V 42: Pos(0.16953, -0.709618, 0.020093) Norm(0.2492, -0.870253, -0.424923) UV(0.647624, 0.0)
V 43: Pos(0.16953, 0.207044, 0.020093) Norm(0.24987, 0.873883, -0.417005) UV(0.647624, 0.521575)
V 44: Pos(-0.049131, -0.709618, 0.535225) Norm(-0.196826, -0.783515, 0.589376) UV(0.0, 0.521684)
V 45: Pos(0.16953, -0.709618, 0.020093) Norm(0.2492, -0.870253, -0.424923) UV(0.306911, 0.521586)
V 46: Pos(0.154366, -0.422368, -0.04817) Norm(0.073067, -0.106497, -0.991625) UV(0.685505, 0.163524)
V 47: Pos(0.154366, -0.062626, -0.04817) Norm(0.071923, 0.11404, -0.990869) UV(0.685505, 0.368215)
V 48: Pos(-0.049131, 0.207044, 0.535225) Norm(-0.195195, 0.782445, 0.591336) UV(0.0, 9.8e-05)
V 49: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.266327, 0.903586)
V 50: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.294091, 0.521694)
V 51: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.294091, 0.903596)
V 52: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.323209, 0.521684)
V 53: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(0.294091, 0.641843)
V 54: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.294091, 0.521684)
V 55: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.238239, 0.903578)
V 56: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(0.266327, 0.521735)
V 57: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(0.266327, 0.903629)
V 58: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(0.323209, 0.762002)
V 59: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.294091, 0.641843)
V 60: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.323209, 0.641843)
V 61: Pos(0.468575, 0.103404, -0.135202) Norm(0.468342, 0.577346, -0.668826) UV(0.804635, 0.381902)
V 62: Pos(0.26061, -0.567769, -0.098532) Norm(-0.668841, -0.57736, -0.468302) UV(0.685505, 0.763804)
V 63: Pos(0.26061, 0.103404, -0.098532) Norm(-0.66884, 0.577347, -0.468319) UV(0.685505, 0.381902)
V 64: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.685505, 0.381902)
V 65: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.804635, 0.0)
V 66: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.804635, 0.381902)
V 67: Pos(0.243142, 0.221328, -0.083115) Norm(-0.577358, -0.577347, 0.577347) UV(0.137905, 0.887434)
V 68: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.162777, 0.850968)
V 69: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.162777, 0.887438)
V 70: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.65141, 0.774464)
V 71: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.6765, 0.715234)
V 72: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.6765, 0.774464)
V 73: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.65141, 0.870164)
V 74: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.676393, 0.83371)
V 75: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.676393, 0.870179)
V 76: Pos(0.347237, 0.221328, -0.083115) Norm(0.577359, -0.577332, 0.577359) UV(0.65141, 0.833694)
V 77: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.6765, 0.774464)
V 78: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.6765, 0.833694)
V 79: Pos(0.347237, 0.285422, -0.127209) Norm(0.577359, 0.577332, -0.577359) UV(0.330561, 0.821176)
V 80: Pos(0.243142, 0.221328, -0.127209) Norm(-0.577359, -0.577332, -0.577359) UV(0.294091, 0.762002)
V 81: Pos(0.243142, 0.285422, -0.127209) Norm(-0.577358, 0.577347, -0.577347) UV(0.330561, 0.762002)
V 82: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.294091, 0.821176)
V 83: Pos(0.347237, 0.221328, -0.083115) Norm(0.577359, -0.577332, 0.577359) UV(0.330561, 0.880351)
V 84: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.294091, 0.880351)
V 85: Pos(0.02036, -0.14665, 0.03328) Norm(-0.577341, 0.577354, 0.577356) UV(0.582523, 0.618734)
V 86: Pos(0.14154, -0.14665, 0.03328) Norm(0.584307, 0.579838, 0.567779) UV(0.068952, 0.850964)
V 87: Pos(0.14154, -0.316218, 0.03328) Norm(0.577339, -0.577359, 0.577353) UV(0.673017, 0.618734)
V 88: Pos(0.02036, -0.316218, 0.03328) Norm(-0.577372, -0.57734, 0.57734) UV(0.137905, 0.850964)
V 89: Pos(0.14154, -0.316218, -0.12644) Norm(0.583322, -0.568894, -0.579737) UV(0.65141, 0.908205)
V 90: Pos(0.02036, -0.316218, 0.03328) Norm(-0.577372, -0.57734, 0.57734) UV(0.582523, 0.715234)
V 91: Pos(-0.614892, 0.561483, 0.284424) Norm(-0.577355, 0.577371, 0.577325) UV(0.804635, 0.763804)
V 92: Pos(-0.0362, 0.561483, 0.284424) Norm(0.57734, 0.577355, 0.577355) UV(0.0, 0.521684)
V 93: Pos(-0.0362, 0.342792, 0.284424) Norm(0.577355, -0.577371, 0.577325) UV(0.929069, 0.657935)
V 94: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.344284, 0.522217)
V 95: Pos(-0.0362, 0.342792, -0.134268) Norm(0.57734, -0.577355, -0.577355) UV(0.804635, 0.657935)
V 96: Pos(-0.614892, 0.342792, 0.284424) Norm(-0.57734, -0.577355, 0.577355) UV(0.929072, 0.0)
V 97: Pos(-0.049131, -0.709618, 0.535225) Norm(-0.196826, -0.783515, 0.589376) UV(0.344284, 0.000642)
V 98: Pos(0.269496, 0.103404, -0.048135) Norm(-0.468342, 0.577346, 0.668826) UV(0.266327, 0.521684)
V 99: Pos(0.477461, 0.103404, -0.084805) Norm(0.66884, 0.577347, 0.468319) UV(0.323209, 0.641843)
V 100: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.238239, 0.521684)
V 101: Pos(0.477461, -0.567769, -0.084805) Norm(0.668841, -0.57736, 0.468302) UV(0.294091, 0.762002)
V 102: Pos(0.468575, -0.567769, -0.135202) Norm(0.468336, -0.577333, -0.668841) UV(0.804635, 0.763804)
V 103: Pos(0.269496, -0.567769, -0.048135) Norm(-0.468336, -0.577333, 0.668841) UV(0.685505, 0.0)
V 104: Pos(0.243142, 0.285422, -0.083115) Norm(-0.577359, 0.577332, 0.577359) UV(0.137905, 0.850964)
V 105: Pos(0.347237, 0.285422, -0.083115) Norm(0.577358, 0.577347, 0.577347) UV(0.65141, 0.715234)
V 106: Pos(0.243142, 0.221328, -0.083115) Norm(-0.577358, -0.577347, 0.577347) UV(0.65141, 0.774464)
V 107: Pos(0.347237, 0.221328, -0.127209) Norm(0.577358, -0.577347, -0.577347) UV(0.294091, 0.821176)
V 108: Pos(0.243142, 0.221328, -0.083115) Norm(-0.577358, -0.577347, 0.577347) UV(0.330561, 0.821176)
===Triangles (Indices): 60
Tri: 0 1 2
Tri: 3 4 5
Tri: 6 7 8
Tri: 9 10 11
Tri: 12 13 14
Tri: 15 16 17
Tri: 18 19 20
Tri: 21 22 23
Tri: 24 25 26
Tri: 27 28 29
Tri: 30 31 32
Tri: 33 34 35
Tri: 36 37 38
Tri: 39 40 37
Tri: 41 42 43
Tri: 44 38 45
Tri: 43 46 47
Tri: 39 44 48
Tri: 49 50 51
Tri: 52 53 54
Tri: 55 56 57
Tri: 58 59 60
Tri: 61 62 63
Tri: 64 65 66
Tri: 67 68 69
Tri: 70 71 72
Tri: 73 74 75
Tri: 76 77 78
Tri: 79 80 81
Tri: 82 83 84
Tri: 0 85 1
Tri: 3 86 4
Tri: 6 87 7
Tri: 9 88 10
Tri: 12 89 13
Tri: 15 90 16
Tri: 18 91 19
Tri: 21 92 22
Tri: 24 93 25
Tri: 27 94 28
Tri: 30 95 31
Tri: 33 96 34
Tri: 36 39 37
Tri: 39 48 40
Tri: 41 97 42
Tri: 44 36 38
Tri: 43 42 46
Tri: 39 36 44
Tri: 49 98 50
Tri: 52 99 53
Tri: 55 100 56
Tri: 58 101 59
Tri: 61 102 62
Tri: 64 103 65
Tri: 67 104 68
Tri: 70 105 71
Tri: 73 76 74
Tri: 76 106 77
Tri: 79 107 80
Tri: 82 108 83

BIN
resources/w/interior/computer_texture002.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -7,6 +7,7 @@
#include "GameConstants.h" #include "GameConstants.h"
#include "Environment.h" #include "Environment.h"
namespace ZL { namespace ZL {
const float ATTACK_COOLDOWN_TIME = 1.6f; const float ATTACK_COOLDOWN_TIME = 1.6f;
@ -94,6 +95,15 @@ Eigen::Vector3f Character::getCurrentNavigationTarget() const
return walkTarget; return walkTarget;
} }
void Character::stopInPlace()
{
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
requestedWalkTarget = walkTarget;
pathWaypoints.clear();
currentWaypointIndex = 0;
onArrivedCallback = nullptr;
}
void Character::forceReplan() void Character::forceReplan()
{ {
if (!pathPlanner) { if (!pathPlanner) {
@ -343,6 +353,18 @@ void Character::update(int64_t deltaMs) {
} }
if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDrift > 0.f) {
homeDriftCheckTimer -= static_cast<float>(deltaMs) / 1000.f;
if (homeDriftCheckTimer <= 0.f) {
homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;
Eigen::Vector3f toHome = homePosition - position;
toHome.y() = 0.f;
if (!isMoving() && toHome.norm() > maxHomeDrift) {
setTarget(homePosition);
}
}
}
if (hitSparkEmitter.isConfigured()) { if (hitSparkEmitter.isConfigured()) {
hitSparkEmitter.update(static_cast<float>(deltaMs)); hitSparkEmitter.update(static_cast<float>(deltaMs));
} }

View File

@ -61,6 +61,10 @@ public:
const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; } const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
Eigen::Vector3f getCurrentNavigationTarget() const; Eigen::Vector3f getCurrentNavigationTarget() const;
void forceReplan(); void forceReplan();
// Cancels any active walk target and clears waypoints so the character
// stays at its current position. Used when an external push moves the
// character and we don't want it to walk back to where it was.
void stopInPlace();
// attackDirection is a world-space horizontal vector pointing from the // attackDirection is a world-space horizontal vector pointing from the
@ -110,6 +114,13 @@ public:
float hp = 200.f; float hp = 200.f;
float initialHp = 200.f; float initialHp = 200.f;
// The position the NPC should return to if pushed too far away.
// Initialised to the spawn position; updated whenever a script walk command is issued.
Eigen::Vector3f homePosition = Eigen::Vector3f::Zero();
// Maximum allowed drift from homePosition before a return walk is triggered (metres).
// Set to 0 to disable drift recovery for this character.
float maxHomeDrift = 3.0f;
int battle_state = 0; int battle_state = 0;
bool resetAnim = false; bool resetAnim = false;
@ -163,6 +174,9 @@ private:
static constexpr float WALK_THRESHOLD = 0.05f; static constexpr float WALK_THRESHOLD = 0.05f;
static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f; static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
static constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f;
float homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;
// Returns the animation state to actually play/draw, falling back to IDLE // Returns the animation state to actually play/draw, falling back to IDLE
// if the requested state has no loaded animation. // if the requested state has no loaded animation.

View File

@ -233,9 +233,7 @@ namespace ZL
}; };
uniInteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_interior.json";
uniInteriorParams.teleportsJsonPath = "resources/config2/teleports.json";
uniInteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_interior.json"; uniInteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_interior.json";
uniInteriorParams.scriptPath = "resources/start_uni_interior.lua"; uniInteriorParams.scriptPath = "resources/start_uni_interior.lua";
uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior.json"; uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior.json";

View File

@ -111,6 +111,7 @@ namespace ZL
npcs = GameObjectLoader::loadAndCreate_Npcs(params.npcsJsonPath, CONST_ZIP_FILE); npcs = GameObjectLoader::loadAndCreate_Npcs(params.npcsJsonPath, CONST_ZIP_FILE);
for (auto& npc : npcs) { for (auto& npc : npcs) {
if (!npc) continue; if (!npc) continue;
npc->homePosition = Eigen::Vector3f(npc->position.x(), 0.f, npc->position.z());
if (npc->canAttack) { if (npc->canAttack) {
npc->setupHitSparks(sparkTexture); npc->setupHitSparks(sparkTexture);
npc->attackTarget = player.get(); npc->attackTarget = player.get();
@ -297,6 +298,7 @@ namespace ZL
const auto addCharacter = [&](const Character* other) { const auto addCharacter = [&](const Character* other) {
if (!other || other == self) return; if (!other || other == self) return;
if (other->hp <= 0.f || !other->enabled) return; if (other->hp <= 0.f || !other->enabled) return;
if (other->isMoving()) return;
if (distancePointToSegmentXZ(other->position, start, end) > kDynamicObstacleInfluenceDist) { if (distancePointToSegmentXZ(other->position, start, end) > kDynamicObstacleInfluenceDist) {
return; return;
@ -304,7 +306,7 @@ namespace ZL
PathFinder::DynamicObstacle obs; PathFinder::DynamicObstacle obs;
obs.position = Eigen::Vector3f(other->position.x(), navigation->getFloorY(), other->position.z()); obs.position = Eigen::Vector3f(other->position.x(), navigation->getFloorY(), other->position.z());
obs.radius = (std::max)(0.0f, other->collisionRadius); obs.radius = (std::max)(0.0f, other->collisionRadius * 0.6f);
dynamicObstacles.push_back(obs); dynamicObstacles.push_back(obs);
}; };
@ -313,7 +315,7 @@ namespace ZL
addCharacter(npc.get()); addCharacter(npc.get());
} }
return navigation->findPath(start, end, dynamicObstacles); return navigation->findPathToNearest(start, end, dynamicObstacles);
}; };
}; };
@ -981,6 +983,30 @@ namespace ZL
return navigation->setAreaAvailable(areaName, available); return navigation->setAreaAvailable(areaName, available);
} }
void Location::nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom)
{
if (!standing || standing->isPlayer) return;
if (!navigation || !navigation->isReady()) return;
static constexpr float kNudgeDist = 1.2f;
Eigen::Vector3f dir = standing->position - awayFrom;
dir.y() = 0.f;
if (dir.norm() < 1e-3f) dir = Eigen::Vector3f(1.f, 0.f, 0.f);
else dir.normalize();
const float angles[] = { 0.f, static_cast<float>(M_PI * 0.5), static_cast<float>(-M_PI * 0.5), static_cast<float>(M_PI) };
for (float angle : angles) {
Eigen::Vector3f rotated = Eigen::AngleAxisf(angle, Eigen::Vector3f::UnitY()) * dir;
Eigen::Vector3f candidate = standing->position + rotated * kNudgeDist;
candidate.y() = 0.f;
if (navigation->isWalkable(candidate)) {
standing->setTarget(candidate);
return;
}
}
}
void Location::resolveCharacterCollisions() void Location::resolveCharacterCollisions()
{ {
std::vector<Character*> characters; std::vector<Character*> characters;
@ -1039,6 +1065,9 @@ namespace ZL
newB.z() += normal.y() * push; newB.z() += normal.y() * push;
newB.y() = 0.f; newB.y() = 0.f;
const bool aWasMoving = a->isMoving();
const bool bWasMoving = b->isMoving();
if (navigation && navigation->isReady()) { if (navigation && navigation->isReady()) {
const bool aOk = navigation->isWalkable(newA); const bool aOk = navigation->isWalkable(newA);
const bool bOk = navigation->isWalkable(newB); const bool bOk = navigation->isWalkable(newB);
@ -1058,6 +1087,15 @@ namespace ZL
a->position = newA; a->position = newA;
b->position = newB; b->position = newB;
} }
if (a->isPlayer && !aWasMoving) a->stopInPlace();
if (b->isPlayer && !bWasMoving) b->stopInPlace();
if (aWasMoving && !bWasMoving) {
nudgeCharacterAside(b, a->position);
} else if (bWasMoving && !aWasMoving) {
nudgeCharacterAside(a, b->position);
}
} }
} }
} }
@ -1066,8 +1104,8 @@ namespace ZL
void Location::updateDynamicReplans(int64_t deltaMs) void Location::updateDynamicReplans(int64_t deltaMs)
{ {
static constexpr float kMovedEps = 0.05f; static constexpr float kMovedEps = 0.05f;
static constexpr float kReplanTriggerDist = 1.1f; static constexpr float kReplanTriggerDist = 1.8f;
static constexpr int64_t kReplanCooldownMs = 300; static constexpr int64_t kReplanCooldownMs = 500;
for (auto it = replanCooldownRemainingMs.begin(); it != replanCooldownRemainingMs.end();) { for (auto it = replanCooldownRemainingMs.begin(); it != replanCooldownRemainingMs.end();) {
it->second -= deltaMs; it->second -= deltaMs;

View File

@ -151,6 +151,7 @@ namespace ZL
void loadTeleportZones(const std::string& jsonPath, const char* zipFile); void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
void loadTriggerZones(const std::string& jsonPath, const char* zipFile); void loadTriggerZones(const std::string& jsonPath, const char* zipFile);
void updateTriggerZones(const Eigen::Vector3f& playerPos); void updateTriggerZones(const Eigen::Vector3f& playerPos);
void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom);
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions; std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs; std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;

View File

@ -57,6 +57,7 @@ namespace ZL {
} }
}; };
} }
npcs[index]->homePosition = Eigen::Vector3f(x, 0.f, z);
npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb)); npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb));
}); });
@ -104,7 +105,7 @@ namespace ZL {
api.set_function("set_object_rotation", [game](const std::string& objectName, float value) { api.set_function("set_object_rotation", [game](const std::string& objectName, float value) {
for (auto& intObj : game->interactiveObjects) { for (auto& intObj : game->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.rotationY = value; intObj.rotationY = value * static_cast<float>(M_PI) / 180.f;
std::cout << "[script] set_object_rotation: " << objectName << " " << value << std::endl; std::cout << "[script] set_object_rotation: " << objectName << " " << value << std::endl;
return; return;
} }
@ -316,7 +317,7 @@ namespace ZL {
npcs[index]->setTarget(pos); npcs[index]->setTarget(pos);
}); });
// npc_set_rotation(index, angle) — sets NPC facing angle around Y axis (radians). // npc_set_rotation(index, angle) — sets NPC facing angle around Y axis (degrees).
api.set_function("npc_set_rotation", api.set_function("npc_set_rotation",
[game](int index, float angle) { [game](int index, float angle) {
auto& npcs = game->npcs; auto& npcs = game->npcs;
@ -324,8 +325,9 @@ namespace ZL {
std::cerr << "[script] npc_set_rotation: index " << index << " out of range\n"; std::cerr << "[script] npc_set_rotation: index " << index << " out of range\n";
return; return;
} }
npcs[index]->facingAngle = angle; const float rad = angle * static_cast<float>(M_PI) / 180.f;
npcs[index]->targetFacingAngle = angle; npcs[index]->facingAngle = rad;
npcs[index]->targetFacingAngle = rad;
}); });
// set_npc_enabled(index, enabled) // set_npc_enabled(index, enabled)

View File

@ -119,13 +119,14 @@ namespace ZL {
else else
obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath); obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath);
constexpr float kDeg2Rad = static_cast<float>(M_PI) / 180.0f;
Eigen::Quaternionf rot = Eigen::Quaternionf::Identity(); Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
if (data.rotationX != 0.0f) if (data.rotationX != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX, Eigen::Vector3f::UnitX())) * rot; rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX * kDeg2Rad, Eigen::Vector3f::UnitX())) * rot;
if (data.rotationY != 0.0f) if (data.rotationY != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY, Eigen::Vector3f::UnitY())) * rot; rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY * kDeg2Rad, Eigen::Vector3f::UnitY())) * rot;
if (data.rotationZ != 0.0f) if (data.rotationZ != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ, Eigen::Vector3f::UnitZ())) * rot; rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ * kDeg2Rad, Eigen::Vector3f::UnitZ())) * rot;
obj.mesh.data.RotateByMatrix(rot.toRotationMatrix()); obj.mesh.data.RotateByMatrix(rot.toRotationMatrix());
if (data.scale != 1.0f) if (data.scale != 1.0f)
@ -262,7 +263,7 @@ namespace ZL {
data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast<float>(M_PI) / 180.0f; data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast<float>(M_PI) / 180.0f; data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast<float>(M_PI) / 180.0f; data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.facingAngle = item.value("facingAngle", 0.0f); data.facingAngle = item.value("facingAngle", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.hp = item.value("hp", 100.0f); data.hp = item.value("hp", 100.0f);
data.canAttack = item.value("canAttack", false); data.canAttack = item.value("canAttack", false);
data.enabled = item.value("enabled", true); data.enabled = item.value("enabled", true);

View File

@ -397,6 +397,124 @@ std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
return path; return path;
} }
std::vector<Eigen::Vector3f> PathFinder::findNearestReachableImpl(
const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& walkableGrid) const
{
if (!ready || walkableGrid.empty()) return {};
Cell startCell;
Cell endCell;
if (!findNearestWalkableCell(start, startCell, walkableGrid)) return {};
if (!findNearestWalkableCell(end, endCell, walkableGrid)) return {};
struct QueueNode {
int index = 0;
float priority = 0.0f;
bool operator<(const QueueNode& other) const { return priority > other.priority; }
};
const int cellCount = gridWidth * gridDepth;
std::vector<float> cost(static_cast<size_t>(cellCount), INF_COST);
std::vector<int> cameFrom(static_cast<size_t>(cellCount), -1);
std::priority_queue<QueueNode> open;
const int startIndex = indexOf(startCell);
const int endIndex = indexOf(endCell);
cost[static_cast<size_t>(startIndex)] = 0.f;
open.push({ startIndex, 0.f });
int bestIndex = startIndex;
float bestDist = distanceCells(startCell, endCell);
static const int offsets[8][2] = {
{ 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 },
{ 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 }
};
while (!open.empty()) {
const QueueNode current = open.top();
open.pop();
if (current.index == endIndex) {
bestIndex = endIndex;
break;
}
if (cost[static_cast<size_t>(current.index)] < current.priority - distanceCells({ current.index % gridWidth, current.index / gridWidth }, endCell)) {
continue; // stale entry
}
const Cell currentCell{ current.index % gridWidth, current.index / gridWidth };
const float d = distanceCells(currentCell, endCell);
if (d < bestDist) {
bestDist = d;
bestIndex = current.index;
}
for (const auto& offset : offsets) {
Cell next{ currentCell.x + offset[0], currentCell.z + offset[1] };
if (!isCellWalkable(next, walkableGrid)) continue;
const bool diagonal = offset[0] != 0 && offset[1] != 0;
if (diagonal) {
Cell h{ currentCell.x + offset[0], currentCell.z };
Cell v{ currentCell.x, currentCell.z + offset[1] };
if (!isCellWalkable(h, walkableGrid) || !isCellWalkable(v, walkableGrid)) continue;
}
const int nextIndex = indexOf(next);
const float stepCost = diagonal ? 1.41421356f : 1.0f;
const float newCost = cost[static_cast<size_t>(current.index)] + stepCost;
if (newCost >= cost[static_cast<size_t>(nextIndex)]) continue;
cost[static_cast<size_t>(nextIndex)] = newCost;
cameFrom[static_cast<size_t>(nextIndex)] = current.index;
open.push({ nextIndex, newCost + distanceCells(next, endCell) });
}
}
if (bestIndex == startIndex) return {};
std::vector<Cell> cells;
for (int cur = bestIndex; cur != -1; cur = cameFrom[static_cast<size_t>(cur)]) {
cells.push_back({ cur % gridWidth, cur / gridWidth });
if (cur == startIndex) break;
}
std::reverse(cells.begin(), cells.end());
cells = smoothCells(cells, walkableGrid);
std::vector<Eigen::Vector3f> path;
path.reserve(cells.size());
for (const Cell& cell : cells) path.push_back(cellCenter(cell));
if (!path.empty() && (path.front() - Eigen::Vector3f(start.x(), floorY, start.z())).norm() < cellSize * 0.75f)
path.erase(path.begin());
return path;
}
std::vector<Eigen::Vector3f> PathFinder::findPathToNearest(
const Eigen::Vector3f& start, const Eigen::Vector3f& end) const
{
auto path = findPath(start, end);
if (!path.empty()) return path;
return findNearestReachableImpl(start, end, walkable);
}
std::vector<Eigen::Vector3f> PathFinder::findPathToNearest(
const Eigen::Vector3f& start, const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const
{
auto path = findPath(start, end, dynamicObstacles);
if (!path.empty()) return path;
path = findPath(start, end);
if (!path.empty()) return path;
return findNearestReachableImpl(start, end, walkable);
}
bool PathFinder::setAreaAvailable(const std::string& areaName, bool available) bool PathFinder::setAreaAvailable(const std::string& areaName, bool available)
{ {
bool found = false; bool found = false;

View File

@ -41,6 +41,14 @@ public:
const Eigen::Vector3f& end, const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const; const std::vector<DynamicObstacle>& dynamicObstacles) const;
// Like findPath, but when the destination is unreachable the character
// walks as close as possible instead of not moving at all.
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
const Eigen::Vector3f& end) const;
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const;
bool setAreaAvailable(const std::string& areaName, bool available); bool setAreaAvailable(const std::string& areaName, bool available);
bool isReady() const { return ready; } bool isReady() const { return ready; }
bool isWalkable(const Eigen::Vector3f& point) const; bool isWalkable(const Eigen::Vector3f& point) const;
@ -95,6 +103,12 @@ private:
bool findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const; bool findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const;
bool hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const; bool hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const;
std::vector<Cell> smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const; std::vector<Cell> smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const;
// A* that runs to exhaustion and returns a path to the reachable cell
// geometrically closest to end when end itself is unreachable.
std::vector<Eigen::Vector3f> findNearestReachableImpl(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& walkableGrid) const;
}; };
} // namespace ZL } // namespace ZL