redraw textures and added new async task system

This commit is contained in:
TorgaW 2024-04-21 23:47:45 +03:00
parent 815422f8e4
commit d1c3766c98
9 changed files with 555 additions and 67 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -68,10 +68,11 @@ function setupInGameSelector() {
// npccc.moveTo(new Point2D(getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getX(), getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getY()));
let tile = getNavigationGridTile(t.x, t.y);
if(tile.isObstacle) return;
npccc.moveTo(new PointInt2D(tile.position.getX(), tile.position.getY()),
(cb)=>{
console.log(cb);
});
npccc.startTask(new PointInt2D(tile.position.getX(), tile.position.getY()));
// npccc.moveTo(new PointInt2D(tile.position.getX(), tile.position.getY()),
// (cb)=>{
// console.log(cb);
// });
}
}

View File

@ -0,0 +1,447 @@
import { PointInt2D } from "../../Utils/Math.utils";
// import { findPathOnNavigationGridIfExists } from "../../World/NavigationGrid/NavigationGrid";
import { NPCController } from "../NPCController/NPCController";
// import { NPCProto } from "../NPCProto/NPCProto";
async function NPCJobFunctor(props, action) {
return new NPCActionCallbackResult();
}
/**
*
* @param {NPCActionCallbackResult} lastActionResult
*/
function NPCActionCallback(lastActionResult) {}
/**
*
* @param {NPCTaskCallbackResult} taskResult
*/
function NPCTaskCallback(taskResult) {}
export class NPCActionCallbackResult {
/**
* status of this action. -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
*/
status = -1;
statusText = "";
/**
* @type {NPCAction}
*/
action = undefined;
/**
* statuses: -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
* @param {Number} status
* @param {String} text
* @param {NPCAction} action
*/
constructor(status, text = "", action = undefined) {
this.status = status;
this.statusText = text;
this.action = action;
}
clone() {
return new NPCActionCallbackResult(this.status, this.statusText, this.action);
}
}
export class NPCTaskCallbackResult {
/**
* status of this task. -1 - error, 0 - completed, 1 - can not be completed, 2 - reset
*/
status = -1;
statusText = "";
/**
* @type {NPCActionCallbackResult}
*/
lastActionCallbackResult;
/**
* @type {NPCTask}
*/
task = undefined;
/**
*
* @param {Number} status
* @param {String} statusText
* @param {NPCActionCallbackResult} lastActionCallbackResult
* @param {NPCTask} task
*/
constructor(status, statusText, lastActionCallbackResult, task) {
this.status = status;
this.statusText = statusText;
this.lastActionCallbackResult = lastActionCallbackResult;
this.task = task;
}
clone()
{
return new NPCTaskCallbackResult(this.status, this.statusText, this.lastActionCallbackResult, this.task);
}
}
/**
* NPC Action is the smallest part of NPC's behavior.
* There are 2 types of actions:
* 0: move to tile
* 1: do something
*/
export class NPCAction {
//invalid action type by default
type = -1;
//target position
targetPosition = new PointInt2D();
/**
* target job function
* @type {NPCJobFunctor}
*/
targetJob;
/**
* these props will passed to job function on it's execution
*/
targetJobProps;
/**
* status of this action. -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
*/
status = -1;
/**
* @type {NPCProto}
*/
owner = null;
/**
* will this task block chain of execution
*/
skipToNextIgnoringStatus = false;
usePause = false;
pauseTime = 0;
/**
* NPC Action is the smallest part of NPC's behavior.
* There are 2 types of actions:
* 0: move to tile
* 1: do something
* @param {Number} type
* @param {PointInt2D} pos
* @param {NPCJobFunctor} job
* @param {any} jobProps
* @param {NPCController} owner
* @param {Boolean} skipToNext
* @param {Boolean} usePause
* @param {Number} pauseTime
*/
constructor(type, pos, job, jobProps = {}, owner = null, skipToNext = false, usePause = false, pauseTime = 0) {
this.type = type;
this.targetPosition = pos;
this.targetJob = job;
this.targetJobProps = jobProps;
this.status = 0;
this.owner = owner;
this.skipToNextIgnoringStatus = skipToNext;
this.usePause = usePause;
this.pauseTime = pauseTime;
}
}
/**
* NPC Task is a container with NPC Actions. It is like a timeline with frames (frames are NPC Actions)
*/
export class NPCTask {
/**
* @type {Array<NPCAction>}
*/
actionsContainer = [];
_currentIndex = 0;
_startIndex = 0;
/**
* @type {NPCController}
*/
owner = null;
//is task active and will execute actions
active = false;
//will task start again after completion
loop = false;
onPause = false;
_lastActionCompleted = false;
/**
* @type {NPCActionCallbackResult}
*/
_lastActionCallbackResult;
/**
* @type {NPCActionCallback}
*/
actionCallback;
/**
* @type {NPCTaskCallback}
*/
taskCallback;
_pauseTimeout;
/**
* @param {Array<NPCAction>} actions
* @param {NPCController} owner
* @param {boolean} loop
* @param {NPCActionCallback} actionCallback
* @param {NPCTaskCallback} taskCallback
*/
constructor(actions, owner = null, loop = false, actionCallback = undefined, taskCallback = undefined) {
this.actionsContainer = actions;
this.owner = owner;
this.loop = loop;
this.actionCallback = actionCallback;
this.taskCallback = taskCallback;
}
startTask() {
if (!this.owner || this._lastActionCompleted) return false;
this._currentIndex = this._startIndex;
this.active = true;
return this._pushNewAction();
}
resetTask() {
this.active = false;
this.onPause = false;
this._resetAllActions();
this._currentIndex = this._startIndex;
clearTimeout(this._pauseTimeout);
this.taskCallback(new NPCTaskCallbackResult(2, "reset", this._lastActionCallbackResult.clone(), this));
return true;
}
/**
* set owner for all actions and this task
* @param {NPCController} owner
*/
setOwnerToAll(owner) {
this.owner = owner;
for (const action of this.actionsContainer) {
action.owner = owner;
}
}
handleTaskTick(ticker) {
if (!this.active || this.onPause) return;
if (this._lastActionCompleted) {
this._lastActionCompleted = false;
this.actionCallback(this._lastActionCallbackResult.clone());
if (!this.active) return;
if (!this.actionsContainer[this._currentIndex].skipToNextIgnoringStatus) {
if (this._lastActionCallbackResult.status !== 1) {
this.taskCallback(new NPCTaskCallbackResult(1, "chain blocked by action status", this._lastActionCallbackResult.clone(), this));
this.resetTask();
return;
}
}
//pay attention in timeout logic
if (this.actionsContainer[this._currentIndex].usePause) {
this.onPause = true;
clearTimeout(this._pauseTimeout);
let thisCopy = this;
this._pauseTimeout = setTimeout(() => {
thisCopy.onPause = false;
thisCopy._currentIndex++;
if (thisCopy._currentIndex >= thisCopy.actionsContainer.length) {
if (thisCopy.loop) {
thisCopy._currentIndex = thisCopy._startIndex;
thisCopy._resetAllActions();
thisCopy._pushNewAction();
} else thisCopy.active = false;
} else {
thisCopy._pushNewAction();
}
}, this.actionsContainer[this._currentIndex].pauseTime);
return;
}
//
this._currentIndex++;
if (this._currentIndex >= this.actionsContainer.length) {
if (this.loop) {
this._currentIndex = this._startIndex;
this._resetAllActions();
this._pushNewAction();
} else {
this.active = false;
this.taskCallback(new NPCTaskCallbackResult(0, "finished", this._lastActionCallbackResult.clone(), this));
}
} else {
this._pushNewAction();
}
}
}
_pushNewAction() {
switch (this.actionsContainer[this._currentIndex].type) {
case 0:
this.owner.moveTo(this.actionsContainer[this._currentIndex].targetPosition, (r) => {
this._handleMovementResult(r);
});
break;
case 1:
this.actionsContainer[this._currentIndex].targetJob(this.actionsContainer[this._currentIndex].targetJobProps, this.actionsContainer[this._currentIndex]).then((r) => {
this._handleJobResult(r);
});
break;
default:
return false;
}
return true;
}
_resetAllActions() {
for (const action of this.actionsContainer) {
action.status = 0;
}
}
/**
* @param {NPCActionCallbackResult} resultFromMoving
*/
_handleMovementResult(resultFromMoving) {
this.actionsContainer[this._currentIndex].status = resultFromMoving === 0 ? 1 : resultFromMoving === -1 ? -1 : 2;
this._lastActionCompleted = true;
this._lastActionCallbackResult = new NPCActionCallbackResult(resultFromMoving === 0 ? 1 : resultFromMoving === -1 ? -1 : 2, "", this.actionsContainer[this._currentIndex]);
}
/**
* @param {NPCActionCallbackResult} resultFromJob
*/
_handleJobResult(resultFromJob) {
this._lastActionCallbackResult = resultFromJob;
this._lastActionCompleted = true;
}
}
/**
* @param {NPCTask} task
*/
function TaskChanger(task) {}
export class NPCBehaviorCallbackResult {
/**
* 0 - action callback
* 1 - task callback
* @type {Number}
*/
type;
/**
* status of the callback (see more in taskCallback or actionCallback)
* @type {Number}
*/
status;
/**
* @type {String}
*/
statusText;
/**
* @type {NPCTaskCallbackResult}
*/
taskCallbackResult;
/**
* @type {NPCActionCallbackResult}
*/
actionCallbackResult;
/**
*
* @param {Number} type
* @param {Number} status
* @param {String} statusText
* @param {NPCTaskCallbackResult} taskCallbackResult
* @param {NPCActionCallbackResult} actionCallbackResult
*/
constructor(type, status, statusText, taskCallbackResult, actionCallbackResult) {
this.type = type;
this.status = status;
this.statusText = statusText;
this.taskCallbackResult = taskCallbackResult;
this.actionCallbackResult = actionCallbackResult;
}
}
export class NPCBehavior {
/**
* @type {Map<String, NPCTask>}
*/
taskMap = new Map();
owner = undefined;
isTaskInProgress = false;
currentTask = "";
behaviorCallback = undefined;
/**
* @param {NPCController} owner
*/
constructor(owner, behaviorCallback) {
this.owner = owner;
this.behaviorCallback = behaviorCallback;
}
/**
* @param {String} key
* @param {NPCTask} task
*/
addTask(key, task) {
task.setOwnerToAll(this.owner);
task.actionCallback = this._handleActionCallback.bind(this);
task.taskCallback = this._handleTaskCallback.bind(this);
this.taskMap.set(key, task);
}
/**
* @param {String} key
*/
startTask(key) {
if (this.isTaskInProgress) return false;
if (this.taskMap.get(key).startTask()) {
this.currentTask = key;
this.isTaskInProgress = true;
return true;
}
return false;
}
abortCurrentTask() {
if (this.taskMap.get(this.currentTask)?.resetTask()) {
this.isTaskInProgress = false;
this.currentTask = "";
}
}
/**
*
* @param {String} key
* @param {TaskChanger} changer
*/
changeTask(key, changer) {
changer(this.taskMap.get(key));
}
handleNPCBehaviorTick(ticker) {
this.taskMap.get(this.currentTask)?.handleTaskTick(ticker);
}
/**
*
* @param {NPCTaskCallbackResult} result
*/
_handleTaskCallback(result) {
this.isTaskInProgress = false;
this.behaviorCallback(new NPCBehaviorCallbackResult(1, result.status, result.statusText, result.clone(), undefined));
console.log(result);
}
/**
*
* @param {NPCActionCallbackResult} result
*/
_handleActionCallback(result) {
this.behaviorCallback(new NPCBehaviorCallbackResult(0, result.status, result.statusText, undefined, result.clone()));
console.log(result);
}
}

View File

@ -2,12 +2,19 @@ import { GameObject } from "../../GameObject/GameObject";
import { PointInt2D } from "../../Utils/Math.utils";
import { NavigationPath, PathFinder } from "../../Utils/PathFinding.utils";
import { findPathOnNavigationGridIfExists } from "../../World/NavigationGrid/NavigationGrid";
import { NPCAction, NPCActionCallbackResult, NPCBehavior, NPCTask } from "../NPCBehavior/NPCBehavior";
import { NPCProto } from "../NPCProto/NPCProto";
/**
*
* @param {Number} result
*/
function NavigationCallbackFunctor(result) {}
/**
* NPCController defines NPC behavior. Many NPC can have same NPCController for the same behavior.
*/
export class NPCController extends GameObject
{
export class NPCController extends GameObject {
/**
* NPC controlled by this controller
* @type NPCProto
@ -22,37 +29,63 @@ export class NPCController extends GameObject
navigationFollowMidPoint = false;
navigationCallback = () => {};
constructor(tickAble = true)
{
taskInstanceRef = new NPCTask(
[
new NPCAction(0, new PointInt2D(0, 0), undefined, undefined, undefined, false, true, 1000),
new NPCAction(
1,
undefined,
async (myOwner, action) => {
console.log(myOwner);
return new NPCActionCallbackResult(1, "", action);
},
"Привет!",
undefined,
false,
false,
0
),
],
undefined,
false
);
behavior = new NPCBehavior(this, () => {});
constructor(tickAble = true) {
super(tickAble);
this.behavior.addTask("MoveToCursor", this.taskInstanceRef);
}
/**
* moves NPC to position
* moves NPC to position. callback contains result status: -1 - error happened; 0 - success; 1 - unreachable
* @param {PointInt2D} position
* @param {Function} callback
* @param {NavigationCallbackFunctor} callback
*/
moveTo(position, callback)
{
moveTo(position, callback) {
// let pf = new PathFinder();
// let nPath = pf.findPathIfExist(new PointInt2D(this.controlledNPC.worldPosition.getX(), this.controlledNPC.worldPosition.getY()), position);
let nPath = findPathOnNavigationGridIfExists(new PointInt2D(this.controlledNPC.worldPosition.getX(), this.controlledNPC.worldPosition.getY()), position);
let nPath = findPathOnNavigationGridIfExists(
new PointInt2D(this.controlledNPC.worldPosition.getX(), this.controlledNPC.worldPosition.getY()),
position
);
nPath.then((r) => {
if(r.error)
{
callback("failed");
return;
}
else if (r.result.path.length < 2)
{
callback("success");
if (r.error) {
this.controlledNPC.isMoving = false;
callback(-1); //error
return;
} else if (r.state === 0) {
this.controlledNPC.isMoving = false;
callback(1); //unreachable
return;
}
this.navigationPathQueue = [];
for (let i = r.result.path.length - 1; i > 0; i--) {
this.navigationPathQueue.push(r.result.path[i]);
}
this.navigationCallback = callback;
this.navigationInProgress = true;
this.controlledNPC.isMoving = true;
});
// console.log("boba");
// console.log(nPath);
@ -60,42 +93,37 @@ export class NPCController extends GameObject
/**
*
* @param {NavigationPath} pathToFollow
* @param {Function} callback
* @param {PointInt2D} pos
*/
_moveByPath(pathToFollow, callback)
{
startTask(pos) {
this.behavior.abortCurrentTask();
this.behavior.changeTask("MoveToCursor", (task)=>{
task.actionsContainer[0].targetPosition = pos;
});
this.behavior.startTask("MoveToCursor");
}
tick(ticker)
{
if(this.navigationInProgress)
{
if(!this.navigationFollowMidPoint)
{
tick(ticker) {
this.taskInstanceRef.handleTaskTick(ticker);
if (this.navigationInProgress) {
if (!this.navigationFollowMidPoint) {
let target = this.navigationPathQueue.pop();
if(!target)
{
if (!target) {
this.navigationInProgress = false;
this.navigationCallback("success");
}
else
{
this.controlledNPC.isMoving = false;
this.navigationCallback(0); //success
} else {
this.controlledNPC.worldPosition = target;
this.navigationFollowMidPoint = true;
}
}
else
{
} else {
if (
Math.abs(this.controlledNPC.drawObject.x - this.controlledNPC.worldPosition.getX()) < 0.5 &&
Math.abs(this.controlledNPC.drawObject.y - this.controlledNPC.worldPosition.getY()) < 0.5
)
{
) {
this.navigationFollowMidPoint = false;
}
}
}
}
};
}

View File

@ -2,6 +2,7 @@ import { Rectangle } from "../../../pixi/pixi.mjs";
import { SceneObject } from "../../SceneObjects/SceneObject";
import { Point2D, interpolate } from "../../Utils/Math.utils";
import { getSpriteFromAtlas } from "../../Utils/Sprites.utils";
import { getNavigationGridTile } from "../../World/NavigationGrid/NavigationGrid";
import { NPCController } from "../NPCController/NPCController";
export class NPCProto extends SceneObject
@ -13,6 +14,7 @@ export class NPCProto extends SceneObject
controller = null;
_positionInterpolationSpeed = 30;
_basicPositionInterpolationSpeed = 30;
/**
* path to NPC spritesheet
@ -24,6 +26,8 @@ export class NPCProto extends SceneObject
*/
frame = new Rectangle();
isMoving = false;
/**
* creates new NPC object
* @param {Boolean} tickAble
@ -43,7 +47,15 @@ export class NPCProto extends SceneObject
tick(ticker)
{
if(this.isMoving)
{
let gridTile = getNavigationGridTile(this.worldPosition.getX(), this.worldPosition.getY());
if(gridTile && !gridTile.isObstacle)
{
this._positionInterpolationSpeed = this._basicPositionInterpolationSpeed / gridTile.movementCost;
}
this._positionInterpolation(ticker.deltaMS / 1000 * this._positionInterpolationSpeed);
}
};
_positionInterpolation(delta)

View File

@ -45,22 +45,22 @@ export class NavigationPath {
export class NavigationResult {
/**
* @type Boolean
* @type {Boolean}
*/
error;
/**
* @type String
* @type {String}
*/
errorText;
/**
* @type NavigationPath | undefined
* @type {NavigationPath | undefined}
*/
result;
/**
* 0 - point is unreachable
* 1 - path found
* 2 - error
* @type Int
* @type {Number}
*/
state;
@ -194,14 +194,14 @@ export class PathFinder {
*/
_reconstructPath(cameFrom, current) {
let totalPath = [current.position];
console.log(cameFrom);
// console.log(cameFrom);
let keys = [...cameFrom.keys()];
while (keys.includes(current.id)) {
if (current.id === cameFrom.get(current.id).id) break;
current = cameFrom.get(current.id);
totalPath.push(current.position);
}
return totalPath.reverse();
return totalPath;
}
/**

View File

@ -5,7 +5,7 @@ import { RGBColor, RGBCue } from "../Utils/DataTypes.utils";
let timeElapsed = PRNG() * 37992648739;
// ex: scaling = 10; 1 game minute = 6 real seconds;
let timeScaling = 300.0;
let timeScaling = 100.0;
const gameSecondsInGameMinute = 60;
const gameMinutesInGameHour = 60;
@ -14,7 +14,7 @@ const gameDaysInGameWeek = 7;
const gameWeeksInGameMonth = 4;
const gameMonthsInGameYear = 12;
let dayColor = new RGBColor(255, 255, 255);
// let dayColor = new RGBColor(255, 255, 255);
let currentColor = new RGBColor(0, 0, 0);
let dayColorsCue = new RGBCue(

View File

@ -131,7 +131,7 @@ async function _findPathAsync(start, goal)
export async function findPathOnNavigationGridIfExists(start, goal) {
let r0 = await _findPathAsync(goal, start);
if(!r0.error){
r0.result.path.reverse();
// r0.result.path.reverse();
return r0;
}
let r1 = await _findPathAsync(start, goal);

View File

@ -17,10 +17,10 @@ const WorldChunksStorage = new Map();
/* #### REWRITE PART START ####*/
const terrainSpriteList = {
0: { x: 21, y: 21 }, //water
1: { x: 2, y: 21 }, //sand
0: { x: 20, y: 20 }, //water
1: { x: 2, y: 20 }, //sand
2: { x: 2, y: 2 }, //grass
3: { x: 21, y: 2 }, //stone
3: { x: 20, y: 2 }, //stone
};
const terrainTypeList = {
0: "ter_water", //water
@ -30,9 +30,9 @@ const terrainTypeList = {
};
const terrainNavigationCostList = {
0: 100000000000, //water
1: 3, //sand
2: 2, //grass
3: 1, //stone
1: 1.7, //sand
2: 1, //grass
3: 1.5, //stone
};
const grassVegetationSpriteList = {
0: { x: 10, y: 11 },