diff --git a/blakcomp/function.c b/blakcomp/function.c index caf4b0f150..56b5776631 100644 --- a/blakcomp/function.c +++ b/blakcomp/function.c @@ -178,6 +178,13 @@ id_struct BuiltinIds[] = { {"realtime", I_MISSING, 36, 0, I_CLASS}, {"gameeventengine",I_MISSING, 37, 0, I_CLASS}, {"escapedconvict",I_MISSING, 38, 0, I_CLASS}, +{"MoveCallback", I_MISSING, 39, 0, I_MESSAGE }, +{"iRow", I_MISSING, 40, 0, I_PARAMETER }, +{"iCol", I_MISSING, 41, 0, I_PARAMETER }, +{"iFineRow", I_MISSING, 42, 0, I_PARAMETER }, +{"iFineCol", I_MISSING, 43, 0, I_PARAMETER }, +{"iType", I_MISSING, 44, 0, I_PARAMETER }, +{"iFlags", I_MISSING, 45, 0, I_PARAMETER }, }; int numbuiltins = (sizeof(BuiltinIds)/sizeof(id_struct)); diff --git a/blakserv/adminfn.c b/blakserv/adminfn.c index 7232e570ad..f293c84d17 100644 --- a/blakserv/adminfn.c +++ b/blakserv/adminfn.c @@ -1409,10 +1409,10 @@ void AdminShowMemory(int session_id,admin_parm_type parms[], aprintf("%s\n",TimeStr(GetTime())); for (i=0;iallocated[i]); + aprintf("%-20s %9lu\n",GetMemoryStatName(i),mstat->allocated[i]); total += mstat->allocated[i]; } - aprintf("%-20s %4lu MB\n","-- Total",total/1024/1024); + aprintf("%-20s %6lu MB\n","-- Total",total/1024/1024); aprintf("-------------------------------------------\n"); } diff --git a/blakserv/astar.c b/blakserv/astar.c new file mode 100644 index 0000000000..e1baf753de --- /dev/null +++ b/blakserv/astar.c @@ -0,0 +1,1120 @@ +// Meridian 59, Copyright 1994-2012 Andrew Kirmse and Chris Kirmse. +// All rights reserved. +// +// This software is distributed under a license that is described in +// the LICENSE file that accompanies it. +// +// Meridian is a registered trademark. +/* +* astar.c +* + +Improvements over the old implementation: + * Allocation of gridmemory is only done once when room is loaded + * Node information is split up between persistent data and data + which needs to be cleared for each algorithm and can be by single ZeroMemory + * Whether a node is in the closed set is not saved and looked up by an additional list, + but rather simply saved by a flag on that node. + * Uses a binary heap to store the open set +*/ + +#include "blakserv.h" + +#define LCHILD(x) (2 * x + 1) +#define RCHILD(x) (2 * x + 2) +#define PARENT(x) ((x-1) / 2) + +astar AStar[THREADSCOUNT]; +astar_query_queue AStarQueries = astar_query_queue(QUERYQUEUESIZE); +astar_response_queue AStarResponses = astar_response_queue(RESPONSEQUEUESIZE); + +void AStarThreadProc(astar* Astar); + +#pragma region HEAP +// refers to the i-th element on the heap +#define HEAP(a, i) ((a)->NodesData[i].heapslot) + +__inline bool AStarHeapCheck(astar* Astar, room_type* Room, int i) +{ + int squares = Room->rowshighres * Room->colshighres; + + if (i >= squares) + return true; + + if (!HEAP(Astar, i)) + return true; + + float our = HEAP(Astar, i)->Data->combined; + int lchild = LCHILD(i); + int rchild = RCHILD(i); + + if (lchild < squares) + { + astar_node* node = HEAP(Astar, lchild); + if (node) + { + if (node->Data->combined < our) + return false; + } + } + if (rchild < squares) + { + astar_node* node = HEAP(Astar, rchild); + if (node) + { + if (node->Data->combined < our) + return false; + } + } + + bool retval = AStarHeapCheck(Astar, Room, lchild); + if (!retval) + return false; + + return AStarHeapCheck(Astar, Room, rchild); +} + +__inline void AStarWriteHeapToFile(astar* Astar, room_type* Room) +{ + int rows = Room->rowshighres; + int cols = Room->colshighres; + char* rowstring = (char *)AStarAlloc(Astar, 60000); + FILE *fp = fopen("heapdebug.txt", "a"); + if (fp) + { + int maxlayer = 9; + int treesize = cols * rows; + int rangestart = 0; + int rangesize = 1; + for (int i = 1; i < maxlayer; i++) + { + if (treesize < rangestart + rangesize) + break; + + sprintf(rowstring, "L:%.2i", i); + + for (int k = 0; k < rangesize; k++) + { + astar_node* node = Astar->NodesData[rangestart + k].heapslot; + + if (node) + sprintf(rowstring, "%s|%6.2f|", rowstring, node->Data->combined); + else + sprintf(rowstring, "%s|XXXXXX|", rowstring); + } + + sprintf(rowstring, "%s\n", rowstring); + fputs(rowstring, fp); + + rangestart = rangestart + rangesize; + rangesize *= 2; + } + sprintf(rowstring, "%s\n", rowstring); + fclose(fp); + } + AStarFree(Astar, rowstring, 60000); +} + +__inline void AStarHeapSwap(astar* Astar, int idx1, int idx2) +{ + astar_node* node1 = HEAP(Astar, idx1); + astar_node* node2 = HEAP(Astar, idx2); + HEAP(Astar, idx1) = node2; + HEAP(Astar, idx2) = node1; + node1->Data->heapindex = idx2; + node2->Data->heapindex = idx1; +} + +__inline void AStarHeapMoveUp(astar* Astar, int Index) +{ + int i = Index; + while (i > 0 && HEAP(Astar, i)->Data->combined < HEAP(Astar, PARENT(i))->Data->combined) + { + AStarHeapSwap(Astar, i, PARENT(i)); + i = PARENT(i); + } +} + +__inline void AStarHeapHeapify(astar* Astar, int Index) +{ + int i = Index; + do + { + int min = i; + if (HEAP(Astar, LCHILD(i)) && HEAP(Astar, LCHILD(i))->Data->combined < HEAP(Astar, min)->Data->combined) + min = LCHILD(i); + if (HEAP(Astar, RCHILD(i)) && HEAP(Astar, RCHILD(i))->Data->combined < HEAP(Astar, min)->Data->combined) + min = RCHILD(i); + if (min == i) + break; + AStarHeapSwap(Astar, i, min); + i = min; + } + while (true); +} + +__inline void AStarHeapInsert(astar* Astar, astar_node* Node) +{ + // save index of this node + Node->Data->heapindex = Astar->HeapSize; + + // add node at the end + HEAP(Astar, Node->Data->heapindex) = Node; + + // increment + Astar->HeapSize++; + + // push node up until in place + AStarHeapMoveUp(Astar, Node->Data->heapindex); +} + +__inline void AStarHeapRemoveFirst(astar* Astar) +{ + int lastIdx = Astar->HeapSize - 1; + + // no elements + if (lastIdx < 0) + return; + + // decrement size + Astar->HeapSize--; + + // more than 1 + if (lastIdx > 0) + { + // put last at root + AStarHeapSwap(Astar, 0, lastIdx); + + // zero out the previous root at swapped slot + HEAP(Astar, lastIdx)->Data->heapindex = 0; + HEAP(Astar, lastIdx) = NULL; + + // reorder tree + AStarHeapHeapify(Astar, 0); + } + + // only one, clear head + else + { + HEAP(Astar, 0)->Data->heapindex = 0; + HEAP(Astar, 0) = NULL; + } +} +#pragma endregion + +#pragma region ASTAR +__inline void AStarAddBlockers(astar* Astar) +{ + Blocker* b; + + b = Astar->Room->Room->Blocker; + while (b) + { + // Don't add self. + if (b->ObjectID == Astar->ObjectID) + { + b = b->Next; + continue; + } + + // Get blocker coords + int row = (int)roundf(b->Position.Y / 256.0f); + int col = (int)roundf(b->Position.X / 256.0f); + + // Don't add blockers at the target coords. + if (abs(row - Astar->EndNode->Meta->Row) < DESTBLOCKIGNORE && + abs(col - Astar->EndNode->Meta->Col) < DESTBLOCKIGNORE) + { + b = b->Next; + continue; + } + + //todo: use neighbour[] ptrs here? + // Mark these nodes in A* grid blocked (our coord and +2 highres each dir) + for (int rowoffset = -2; rowoffset < 3; rowoffset++) + { + for (int coloffset = -2; coloffset < 3; coloffset++) + { + int r = row + rowoffset; + int c = col + coloffset; + + // outside + if (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) + continue; + + astar_node* node = &Astar->Room->Grid[r][c]; + node->Data->isBlocked = true; + } + } + b = b->Next; + } +} + +__inline bool AStarCanWalkEdge(astar* Astar, astar_node* Node, astar_node* Neighbour, unsigned short KnowsVal, unsigned short CanVal) +{ + Wall* blockWall; + + bool knows = (((*Node->Edges) & KnowsVal) == KnowsVal); + bool can; + + // not yet cached + if (!knows) + { + // bsp query + can = BSPCanMoveInRoom(Astar->Room->Room, &Node->Meta->Location, &Neighbour->Meta->Location, Astar->ObjectID, false, &blockWall, true); + + // save to cache + *Node->Edges |= (can) ? CanVal : KnowsVal; + } + + // retrieve answer from cache + else + can = (((*Node->Edges) & CanVal) == CanVal); + + return can; +} + +__inline void AStarProcessNeighbour(astar* Astar, astar_node* Node, astar_node* Neighbour, float StepCost, unsigned short KnowsVal, unsigned short CanVal) +{ + // skip any neighbour outside of grid or sector, already processed or blocked + if (!Neighbour || !Neighbour->Leaf || Neighbour->Data->isInClosedList || Neighbour->Data->isBlocked) + return; + + // can't walk edge from node to neighbour + if (!AStarCanWalkEdge(Astar, Node, Neighbour, KnowsVal, CanVal)) + return; + + // heuristic (~ estimated distance from node to end) + // need to do this only once + if (Neighbour->Data->heuristic == 0.0f) + { + float dx = (float)abs(Neighbour->Meta->Col - Astar->EndNode->Meta->Col); + float dy = (float)abs(Neighbour->Meta->Row - Astar->EndNode->Meta->Row); + + // octile-distance + Neighbour->Data->heuristic = + COST * (dx + dy) + (COST_DIAG - 2.0f * COST) * fminf(dx, dy); + + // tie breaker and fixes h(nondiagonal) not lower exact cost + Neighbour->Data->heuristic *= 0.999f; + } + + // CASE 1) + // if this candidate has no parent yet (never visited before) + if (!Neighbour->Data->parent) + { + // cost to candidate is cost to Node + one step + Neighbour->Data->parent = Node; + Neighbour->Data->cost = Node->Data->cost + StepCost; + Neighbour->Data->combined = Neighbour->Data->cost + Neighbour->Data->heuristic; + + // add it sorted to the open list + AStarHeapInsert(Astar, Neighbour); + } + + // CASE 2) + // we already got a path to this candidate + else + { + // our cost to the candidate + float newcost = Node->Data->cost + StepCost; + + // we're cheaper, so update the candidate + // the real cost matters here, not including the heuristic + if (newcost < Neighbour->Data->cost) + { + Neighbour->Data->parent = Node; + Neighbour->Data->cost = newcost; + Neighbour->Data->combined = newcost + Neighbour->Data->heuristic; + + // reorder it upwards in the heap tree, don't care about downordering + // since costs are lower and heuristic is always the same, + // it's guaranteed to be moved up + AStarHeapMoveUp(Astar, Neighbour->Data->heapindex); + } + } +} + +__inline bool AStarProcessFirstOnHeap(astar* Astar) +{ + // shortcuts + astar_node* node = HEAP(Astar, 0); + astar_node* endNode = Astar->EndNode; + + // openlist empty, unreachable + if (!node) + return false; + + // close enough, we're done, path found + if (abs(node->Meta->Col - endNode->Meta->Col) < CLOSEENOUGHDIST && + abs(node->Meta->Row - endNode->Meta->Row) < CLOSEENOUGHDIST) + { + Astar->LastNode = node; + return false; + } + + /****************************************************************/ + + int r, c; + astar_node* n; + + // straight neighbours + + // n + r = node->Meta->Row - 1; + c = node->Meta->Col + 0; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST, EDGECACHE_KNOWS_N, EDGECACHE_CAN_N); + + // e + r = node->Meta->Row + 0; + c = node->Meta->Col + 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST, EDGECACHE_KNOWS_E, EDGECACHE_CAN_E); + + // s + r = node->Meta->Row + 1; + c = node->Meta->Col + 0; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST, EDGECACHE_KNOWS_S, EDGECACHE_CAN_S); + + // w + r = node->Meta->Row + 0; + c = node->Meta->Col - 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST, EDGECACHE_KNOWS_W, EDGECACHE_CAN_W); + + // diagonal neighbours + + // ne + r = node->Meta->Row - 1; + c = node->Meta->Col + 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST_DIAG, EDGECACHE_KNOWS_NE, EDGECACHE_CAN_NE); + + // se + r = node->Meta->Row + 1; + c = node->Meta->Col + 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST_DIAG, EDGECACHE_KNOWS_SE, EDGECACHE_CAN_SE); + + // sw + r = node->Meta->Row + 1; + c = node->Meta->Col - 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST_DIAG, EDGECACHE_KNOWS_SW, EDGECACHE_CAN_SW); + + // nw + r = node->Meta->Row - 1; + c = node->Meta->Col - 1; + n = (r < 0 || c < 0 || r >= Astar->Room->Room->rowshighres || c >= Astar->Room->Room->colshighres) ? NULL : &Astar->Room->Grid[r][c]; + AStarProcessNeighbour(Astar, node, n, COST_DIAG, EDGECACHE_KNOWS_NW, EDGECACHE_CAN_NW); + + /****************************************************************/ + + // mark this node processed + node->Data->isInClosedList = true; + + // remove it from the open list + AStarHeapRemoveFirst(Astar); + + return true; +} + +void AStarWriteGridToFile(astar_room* Room) +{ + int rows = Room->Room->rowshighres; + int cols = Room->Room->colshighres; + + char *rowstring = (char *)malloc(50000); + + FILE *fp = fopen("griddebug.txt", "w"); + if (fp) + { + for (int row = 0; row < rows; row++) + { + sprintf(rowstring, "Row %3i- ", row); + for (int col = 0; col < cols; col++) + { + sprintf(rowstring, "%s|%7.3f|", rowstring, Room->Grid[row][col].Data->combined); + } + sprintf(rowstring, "%s \n", rowstring); + fputs(rowstring, fp); + } + fclose(fp); + } + + free(rowstring); +} + +void AStarInit() +{ + // start workers + for (int i = 0; i < THREADSCOUNT; i++) + { + AStar[i].Commands = new astar_command_queue(COMMANDQUEUESIZE); + AStar[i].Thread = new ::std::thread(AStarThreadProc, &AStar[i]); + } +} + +void AStarShutdown() +{ + // stop workers + for (int i = 0; i < THREADSCOUNT; i++) + AStar[i].IsRunning.store(false); +} +#pragma endregion + +#pragma region THREADING +void AStarHandleLoadRoom(astar* Astar, astar_command_loadroom* Command) +{ + room_type* bsproom = (room_type*)AStarAlloc(Astar, sizeof(room_type)); + + // failed to load room + if (!BSPLoadRoom(Command->file, bsproom, Astar)) + { + AStarFree(Astar, bsproom, sizeof(room_type)); + return; + } + + // set id on our own instance + bsproom->roomdata_id = Command->id; + bsproom->resource_id = 0; + + // create astar room + astar_room* room = (astar_room*)AStarAlloc(Astar, sizeof(astar_room)); + room->Room = bsproom; + + if (bsproom->colshighres > MAXGRIDCOLS || bsproom->rowshighres > MAXGRIDROWS) + eprintf("ALERT!! You tried to load a room with a grid-size beyond allowed limits! AStar will fail."); + + /**********************************************************************/ + + // allocate memory to cache edges (BSP queries) + room->EdgesCacheSize = bsproom->colshighres * bsproom->rowshighres * sizeof(unsigned short); + room->EdgesCache = (unsigned short*)AStarAlloc(Astar, room->EdgesCacheSize); + ZeroMemory(room->EdgesCache, room->EdgesCacheSize); + + /**********************************************************************/ + + // allocate memory for the persistent nodesinfo (typical 2d array by **) + room->Grid = (astar_node**)AStarAlloc(Astar, bsproom->rowshighres * sizeof(astar_node*)); + + // allocate rows + for (int i = 0; i < bsproom->rowshighres; i++) + room->Grid[i] = (astar_node*)AStarAlloc(Astar, bsproom->colshighres * sizeof(astar_node)); + + /**********************************************************************/ + + // setup all squares + for (int i = 0; i < bsproom->rowshighres; i++) + { + int row = (i < MAXGRIDROWS) ? i : 0; + + for (int j = 0; j < bsproom->colshighres; j++) + { + int col = (j < MAXGRIDCOLS) ? j : 0; + int idx1 = row*bsproom->colshighres + col; + int idx2 = i*bsproom->colshighres + j; + + astar_node* node = &room->Grid[i][j]; + + // setup reference to data (costs etc.) in erasable mem area + node->Data = &Astar->NodesData[idx1]; + node->Meta = &Astar->Grid[row][col]; + + // setup reference to edgesdata in edgescache + node->Edges = &room->EdgesCache[idx2]; + + float f1, f2, f3; + + // mark if this square is inside or outside of room + BSPGetHeight(bsproom, &node->Meta->Location, &f1, &f2, &f3, &node->Leaf); + } + } + + /**********************************************************************/ + + // add to map + Astar->Rooms->insert(::std::pair(Command->id, room)); +} + +void AStarHandleUnloadRoom(astar* Astar, astar_command_unloadroom* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->id); + + // not found + if (item == Astar->Rooms->end()) + return; + + astar_room* astarroom = item->second; + room_type* bsproom = item->second->Room; + + // free each row mem + for (int i = 0; i < bsproom->rowshighres; i++) + AStarFree(Astar, astarroom->Grid[i], bsproom->colshighres * sizeof(astar_node)); + + // free rowsmem + AStarFree(Astar, astarroom->Grid, bsproom->rowshighres * sizeof(astar_node*)); + + // free edgescache mem + AStarFree(Astar, astarroom->EdgesCache, astarroom->EdgesCacheSize); + + // free room + BSPFreeRoom(bsproom, Astar); + + // remove from map + Astar->Rooms->erase(item); +} + +void AStarHandleBlockerAdd(astar* Astar, astar_command_blockeradd* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPBlockerAdd( + item->second->Room, + Command->objectid, + &Command->p); +} + +void AStarHandleBlockerRemove(astar* Astar, astar_command_blockerremove* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPBlockerRemove( + item->second->Room, + Command->objectid); +} + +void AStarHandleBlockerMove(astar* Astar, astar_command_blockermove* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPBlockerMove( + item->second->Room, + Command->objectid, + &Command->p); +} + +void AStarHandleBlockerClear(astar* Astar, astar_command_blockerclear* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPBlockerClear(item->second->Room); +} + +void AStarHandleMoveSector(astar* Astar, astar_command_movesector* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPMoveSector( + item->second->Room, + Command->serverid, + Command->floor, + Command->height, + Command->speed); + + // invalidate edges cache + ZeroMemory(item->second->EdgesCache, item->second->EdgesCacheSize); + + // path cache is only in main-instances +} + +void AStarHandleChangeTexture(astar* Astar, astar_command_changetexture* Command) +{ + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Command->roomid); + + // not found + if (item == Astar->Rooms->end()) + return; + + // call BSP function on our room instance + BSPChangeTexture( + item->second->Room, + Command->serverid, + Command->newtexture, + Command->flags); + + // invalidate edges cache + ZeroMemory(item->second->EdgesCache, item->second->EdgesCacheSize); + + // path cache is only in main-instances +} + +void AStarHandleCommand(astar* Astar, astar_command* Command) +{ + switch (Command->commandType()) + { + case astar_command_type::AStarLoadRoom: + AStarHandleLoadRoom(Astar, (astar_command_loadroom*)Command); + break; + case astar_command_type::AStarUnloadRoom: + AStarHandleUnloadRoom(Astar, (astar_command_unloadroom*)Command); + break; + case astar_command_type::AStarBlockerAdd: + AStarHandleBlockerAdd(Astar, (astar_command_blockeradd*)Command); + break; + case astar_command_type::AStarBlockerRemove: + AStarHandleBlockerRemove(Astar, (astar_command_blockerremove*)Command); + break; + case astar_command_type::AStarBlockerMove: + AStarHandleBlockerMove(Astar, (astar_command_blockermove*)Command); + break; + case astar_command_type::AStarBlockerClear: + AStarHandleBlockerClear(Astar, (astar_command_blockerclear*)Command); + break; + case astar_command_type::AStarMoveSector: + AStarHandleMoveSector(Astar, (astar_command_movesector*)Command); + break; + case astar_command_type::AStarChangeTexture: + AStarHandleChangeTexture(Astar, (astar_command_changetexture*)Command); + break; + } + delete Command; +} + +astar_pathresponse* AStarRunPathQuery(astar* Astar, astar_pathquery* Query) +{ + // return value + astar_pathresponse* response = + new astar_pathresponse(Query->roomid, Query->objectid, Query->s, Query->e, Query->flags); + + // lookup room + ::std::map::iterator item = + Astar->Rooms->find(Query->roomid); + + // not found + if (item == Astar->Rooms->end()) + return response; + + astar_room* room = item->second; + room_type* bsproom = room->Room; + + /**********************************************************************/ + + // convert coordinates from ROO floatingpoint to + // highres scale in integers + int startrow = (int)roundf(Query->s.Y / 256.0f); + int startcol = (int)roundf(Query->s.X / 256.0f); + int endrow = (int)roundf(Query->e.Y / 256.0f); + int endcol = (int)roundf(Query->e.X / 256.0f); + + // all 4 values must be within grid array bounds! + if (startrow < 0 || startrow >= bsproom->rowshighres || + startcol < 0 || startcol >= bsproom->colshighres || + endrow < 0 || endrow >= bsproom->rowshighres || + endcol < 0 || endcol >= bsproom->colshighres) + return response; + + /**********************************************************************/ + + // set data on astar + Astar->StartNode = &room->Grid[startrow][startcol]; + Astar->EndNode = &room->Grid[endrow][endcol]; + Astar->Room = room; + Astar->LastNode = NULL; + Astar->ObjectID = Query->objectid; + Astar->HeapSize = 0; + + /**********************************************************************/ + + // prepare non-persistent astar grid data memory + ZeroMemory(Astar->NodesData, Astar->NodesDataSize); + //ZeroMemory(Astar->NodesData, bsproom->rowshighres * bsproom->colshighres * sizeof(astar_node_data)); + + /**********************************************************************/ + + // mark nodes blocked by objects + AStarAddBlockers(Astar); + + // insert startnode into heap-tree + AStarHeapInsert(Astar, Astar->StartNode); + + // the algorithm finishes if we either hit a node close enough to endnode + // or if there is no more entries in the open list (unreachable) + while (AStarProcessFirstOnHeap(Astar)) {} + + //AStarWriteGridToFile(Room); + //AStarWriteHeapToFile(Room); + + /**********************************************************************/ + + // unreachable + if (!Astar->LastNode) + return response; + + // walk back parent pointers from lastnode (=path) + astar_node* node = Astar->LastNode; + while (node) + { + // create path node (use malloc, "not our mem", will be used and freed in mainthread) + astar_path_node* pathnode = (astar_path_node*)malloc(sizeof(astar_path_node)); + + // set values + pathnode->Row = node->Meta->Row; + pathnode->Col = node->Meta->Col; + pathnode->Location = node->Meta->Location; + + // add to path + response->path.push_front(pathnode); + + // process next node + node = node->Data->parent; + } + + return response; +} + +astar_response* AStarRunQuery(astar* Astar, astar_query* Query) +{ + astar_response* ret = NULL; + switch (Query->queryType()) + { + case astar_query_type::AStarPathQuery: + ret = AStarRunPathQuery(Astar, (astar_pathquery*)Query); + break; + } + return ret; +} + +void AStarThreadProc(astar* Astar) +{ + Astar->IsRunning = true; + Astar->IsPaused = false; + Astar->DoPause = false; + Astar->Memory = sizeof(astar); + + /**************************************************************************************/ + /* STARTUP */ + /**************************************************************************************/ + + // memory for astar data erased with each query + Astar->NodesDataSize = NODESDATASQUARES * sizeof(astar_node_data); + Astar->NodesData = (astar_node_data*)AStarAlloc(Astar, Astar->NodesDataSize); + + // basic init values + Astar->StartNode = NULL; + Astar->EndNode = NULL; + Astar->LastNode = NULL; + Astar->ObjectID = 0; + Astar->HeapSize = 0; + Astar->Room = NULL; + + // create rooms list + Astar->Rooms = new astar_rooms(); + + // allocate memory for the persistent grid + Astar->Grid = (astar_node_meta**)AStarAlloc(Astar, MAXGRIDROWS * sizeof(astar_node_meta*)); + + // allocate rows + for (int i = 0; i < MAXGRIDROWS; i++) + Astar->Grid[i] = (astar_node_meta*)AStarAlloc(Astar, MAXGRIDCOLS * sizeof(astar_node_meta)); + + // setup all squares + for (int i = 0; i < MAXGRIDROWS; i++) + { + for (int j = 0; j < MAXGRIDCOLS; j++) + { + int idx = i*MAXGRIDCOLS + j; + + astar_node_meta* node = &Astar->Grid[i][j]; + + // set row/col + node->Row = i; + node->Col = j; + + // floatingpoint coordinates of the center of the square in ROO fineness (for queries) + node->Location.X = j * 256.0f;// +128.0f; + node->Location.Y = i * 256.0f;// +128.0f; + } + } + + /**************************************************************************************/ + /* WORKING */ + /**************************************************************************************/ + + while (Astar->IsRunning.load()) + { + // paused mode + if (Astar->DoPause) + { + // sleep if pause is active + if (Astar->IsPaused) + ::std::this_thread::sleep_for(::std::chrono::milliseconds(WORKERSLEEPTIMEMS)); + + // go to pause-mode + else + { + // handle all remaining commands + while (astar_command* cmd = Astar->Commands->dequeue()) + AStarHandleCommand(Astar, cmd); + + Astar->IsPaused = true; + } + } + + // normal mode + else + { + // make sure pause flag is unset + Astar->IsPaused = false; + + // handle all pending commands + while (astar_command* cmd = Astar->Commands->dequeue()) + AStarHandleCommand(Astar, cmd); + + // try to grab the next query-task + astar_query* query = AStarQueries.dequeue(); + + // if we got one, process it and enqueue the possible response + if (query) + { + astar_response* response = AStarRunQuery(Astar, query); + + if (response) + AStarResponses.enqueue(response); + + delete query; + + // let mainthread know about a new path being ready + PostThreadMessage(main_thread_id, WM_BLAK_MAIN_PATH_READY, 0, 0); + } + + // if there was no query to process, sleep a bit + else + ::std::this_thread::sleep_for(::std::chrono::milliseconds(WORKERSLEEPTIMEMS)); + } + } + + /**************************************************************************************/ + /* SHUTDOWN */ + /**************************************************************************************/ + + AStarFree(Astar, Astar->NodesData, Astar->NodesDataSize); + + // free each meta grid row + for (int i = 0; i < MAXGRIDROWS; i++) + AStarFree(Astar, Astar->Grid[i], MAXGRIDCOLS * sizeof(astar_node_meta)); + + // free meta grid + AStarFree(Astar, Astar->Grid, MAXGRIDROWS * sizeof(astar_node_meta*)); + + // delete rooms (todo: need cleanup of items, but blakserv is likely shutting down..) + delete Astar->Rooms; +} + +void AStarDeliverPathResponse(astar_pathresponse* Response) +{ + room_node* roomnode = GetRoomDataByID(Response->roomid); + V2 p = { 0.0f, 0.0f }; + Wall* blockWall = NULL; + + if (!roomnode) + return; + + // unreachable + if (Response->path.size() == 0) + { + /*******************************************************************/ + // Add to nopath cache + /*******************************************************************/ + // check for resetting nextpathidx + if (roomnode->data.NextNoPathIdx >= NOPATHCACHESIZE) + roomnode->data.NextNoPathIdx = 0; + + // get nopath instance from cache + astar_nopath* nopath = roomnode->data.NoPaths[roomnode->data.NextNoPathIdx]; + roomnode->data.NextNoPathIdx++; + + // update values + nopath->S = Response->s; + nopath->E = Response->e; + nopath->Tick = ::std::chrono::high_resolution_clock::now(); + + // use heuristic + if (BSPGetStepFromHeuristic(&roomnode->data, &Response->s, &Response->e, &p, &Response->flags, Response->objectid)) + BSPInvokeMoveCallback(Response->objectid, MC_HEURISTIC, Response->flags, &p); + + // fully blocked + else + BSPInvokeMoveCallback(Response->objectid, MC_UNREACHABLE, Response->flags, &p); + } + else + { + /*******************************************************************/ + // Add to path cache + /*******************************************************************/ + + // check for resetting nextpathidx + if (roomnode->data.NextPathIdx >= PATHCACHESIZE) + roomnode->data.NextPathIdx = 0; + + // get path from cache and clear it + astar_path* path = roomnode->data.Paths[roomnode->data.NextPathIdx]; + roomnode->data.NextPathIdx++; + + // clear old cached path, use instances in response in our path + BSPClearPath(path); + path->assign(Response->path.begin(), Response->path.end()); + + /*******************************************************************/ + // Deliver next step to kod object by objectid + /*******************************************************************/ + + // unset possible heuristic flags from earlier (unreachable) steps + Response->flags &= ~MF_AVOIDING; + Response->flags &= ~MF_CLOCKWISE; + + // try to do step suggested from path + if (BSPGetStepFromPath(&roomnode->data, path, &path->front()->Location, &path->back()->Location, &p, &Response->flags, Response->objectid)) + BSPInvokeMoveCallback(Response->objectid, MC_NEWPATH, Response->flags, &p); + + // use heuristic + else if (BSPGetStepFromHeuristic(&roomnode->data, &Response->s, &Response->e, &p, &Response->flags, Response->objectid)) + BSPInvokeMoveCallback(Response->objectid, MC_HEURISTIC, Response->flags, &p); + + // fully blocked + else + BSPInvokeMoveCallback(Response->objectid, MC_UNREACHABLE, Response->flags, &p); + } +} + +bool AStarDeliverNextResponse() +{ + astar_response* response = AStarResponses.dequeue(); + + if (!response) + return false; + + switch (response->responseType()) + { + case astar_response_type::AStarPathResponse: + AStarDeliverPathResponse((astar_pathresponse*)response); + break; + } + + delete response; + return true; +} + +void AStarEnqueueCommand(astar_command* Command) +{ + // send copy of command to each worker + for (int i = 0; i < THREADSCOUNT; i++) + AStar[i].Commands->enqueue(Command->clone()); + + // delete the template + delete Command; +} + +void AStarInitGC() +{ + // tell workers to pause + for (int i = 0; i < THREADSCOUNT; i++) + AStar[i].DoPause = true; + + // wait for workers to get in pause mode + // they will process all pending commands first + while (true) + { + bool allpaused = true; + + for (int i = 0; i < THREADSCOUNT; i++) + if (!AStar[i].IsPaused) + allpaused = false; + + if (allpaused) + break; + } + + // clear the query queue, just discard them due to + // invalid (ids) and monsters get their waiting flag reset on GC + while (astar_query* query = AStarQueries.dequeue()) + delete query; + + // clear the response queues, free mem of pathnodes we not gonna use + while (astar_response* response = AStarResponses.dequeue()) + { + if (response->responseType() == AStarPathResponse) + { + astar_pathresponse* pathresponse = (astar_pathresponse*)response; + while (!pathresponse->path.empty()) + { + astar_path_node* node = pathresponse->path.front(); + pathresponse->path.pop_front(); + free(node); + } + } + delete response; + } +} + +void AStarFinishGC() +{ + // tell workers to run again + for (int i = 0; i < THREADSCOUNT; i++) + AStar[i].DoPause = false; +} + +void AStarPerformGC() +{ + // loop through all workers + for (int i = 0; i < THREADSCOUNT; i++) + { + astar* a = &AStar[i]; + + // loop through all rooms of this worker + for (std::map::iterator it = a->Rooms->begin(); it != a->Rooms->end(); ++it) + { + room_type* bsproom = it->second->Room; + Blocker* b = bsproom->Blocker; + + // loop through all blockers and update id + while (b) + { + b->ObjectID = GetObjectByID(b->ObjectID)->garbage_ref; + b = b->Next; + } + } + } +} +#pragma endregion \ No newline at end of file diff --git a/blakserv/astar.h b/blakserv/astar.h new file mode 100644 index 0000000000..9e24443066 --- /dev/null +++ b/blakserv/astar.h @@ -0,0 +1,207 @@ +// Meridian 59, Copyright 1994-2012 Andrew Kirmse and Chris Kirmse. +// All rights reserved. +// +// This software is distributed under a license that is described in +// the LICENSE file that accompanies it. +// +// Meridian is a registered trademark. +/* +* astar.h: +*/ + +#ifndef _ASTAR_H +#define _ASTAR_H + +#include +#include +#include +#include + +/**************************************************************************************************************/ +/* GENERAL */ +/**************************************************************************************************************/ + +#define CLOSEENOUGHDIST 3 // allowed offset in squares to consider a square to be the end +#define DESTBLOCKIGNORE 3 // area around the end which never gets marked blocked by blockers + +/**************************************************************************************************************/ +/* GRID */ +/**************************************************************************************************************/ +#define COST 1.0f // cost of a straight-edge +#define COST_DIAG ((float)M_SQRT2) // cost of a diagonal-edge +#define MAXGRIDROWS 1024 // max. supported highres rows (1 highres = 16 fine) +#define MAXGRIDCOLS 1024 // max. supported highres cols (1 highres = 16 fine) +#define NODESDATASQUARES MAXGRIDROWS * MAXGRIDCOLS // max. supported highres-squares (rows*cols) + +/**************************************************************************************************************/ +/* PATH CACHE */ +/**************************************************************************************************************/ +#define PATHCACHESIZE 16 // how many paths are cached per room +#define PATHCACHEENDTOLERANCE 768.0f // max. distance between end and a cached end to be a match +#define PATHCACHESTARTTOLERANCE 16.0f // max. distance between start and a cached start to be a match + +/**************************************************************************************************************/ +/* NOPATH CACHE */ +/**************************************************************************************************************/ +#define NOPATHCACHESIZE 16 // how many unreachable start-end results are cached per room +#define NOPATHCACHEVALIDDURATIONINS 3 // how long a no-path cache entry is valid (in seconds) +#define NOPATHCACHEENDTOLERANCE 256.0f // max. distance between end and a cached end to be a match +#define NOPATHCACHESTARTTOLERANCE 256.0f // max. distance between start and a cached start to be a match + +/**************************************************************************************************************/ +/* THREADING */ +/**************************************************************************************************************/ +#define THREADSCOUNT 2 // how many astar workers (threads) are used +#define COMMANDQUEUESIZE 3000 // max. elements in command-queues (must be big, e.g. to sync room changes) +#define QUERYQUEUESIZE 100 // max. elements in query-queue +#define RESPONSEQUEUESIZE 100 // max. elements in response-queue +#define WORKERSLEEPTIMEMS 10 // sleep time for workers if they should sleep + +/**************************************************************************************************************/ +/* EDGE CACHE FLAGS */ +/**************************************************************************************************************/ + +#define EDGECACHE_KNOWS_N 0x0001 // set if north-edge is cached +#define EDGECACHE_KNOWS_NE 0x0002 // set if north-east-edge is cached +#define EDGECACHE_KNOWS_E 0x0004 // set if east-edge is cached +#define EDGECACHE_KNOWS_SE 0x0008 // set if south-east-edge is cached +#define EDGECACHE_KNOWS_S 0x0010 // set if south-edge is cached +#define EDGECACHE_KNOWS_SW 0x0020 // set if south-west-edge is cached +#define EDGECACHE_KNOWS_W 0x0040 // set if west-edge is cached +#define EDGECACHE_KNOWS_NW 0x0080 // set if north-west-edge is cached +#define EDGECACHE_CAN_N 0x0101 // set if north-edge is cached and walkable +#define EDGECACHE_CAN_NE 0x0202 // set if north-east-edge is cached and walkable +#define EDGECACHE_CAN_E 0x0404 // set if east-edge is cached and walkable +#define EDGECACHE_CAN_SE 0x0808 // set if south-east-edge is cached and walkable +#define EDGECACHE_CAN_S 0x1010 // set if south-edge is cached and walkable +#define EDGECACHE_CAN_SW 0x2020 // set if south-west-edge is cached and walkable +#define EDGECACHE_CAN_W 0x4040 // set if west-edge is cached and walkable +#define EDGECACHE_CAN_NW 0x8080 // set if north-west-edge is cached and walkable + +/**************************************************************************************************************/ +/* FORWARD DECLARATIONS */ +/**************************************************************************************************************/ + +typedef struct room_type room_type; +typedef struct BspLeaf BspLeaf; +typedef struct astar_node astar_node; + +/**************************************************************************************************************/ +/* STRUCTS */ +/**************************************************************************************************************/ + +typedef struct astar_node_data +{ + float cost; // cost of that node + float heuristic; // heuristic cost of that node + float combined; // sum of cost and heuristic + astar_node* parent; // parent (one of the neighbours) which provides the shortest path to the node + int heapindex; // index of this node in the heap + bool isInClosedList; // true for any evaluated node + bool isBlocked; // true for any node blocked by a blocker + astar_node* heapslot; // provides the n-th memory slot of the heap (unrelated other values/the node) +} astar_node_data; + +/**************************************************************************************************************/ + +typedef struct astar_node_meta +{ + int Row; // row of the node (in highres) + int Col; // column of the node (in highres) + V2 Location; // location (in ROO fineness) +} astar_node_meta; + +/**************************************************************************************************************/ + +typedef struct astar_node +{ + astar_node_meta* Meta; // the common meta data (equal for all nodes of any grid) + astar_node_data* Data; // the data-slot linked with this node + BspLeaf* Leaf; // a possible BSP leaf at the node's location + unsigned short* Edges; // pointer to the edge-cache of this node +} astar_node; + +/**************************************************************************************************************/ + +typedef struct astar_path_node +{ + int Row; // row of the node (in highres) + int Col; // col of the node (in highres) + V2 Location; // location (in ROO fineness) +} astar_path_node; + +/**************************************************************************************************************/ + +typedef struct astar_room +{ + room_type* Room; // the basic bsp room-data + astar_node** Grid; // the astar-grid of this room + unsigned short* EdgesCache; // the edges-cache of this room + int EdgesCacheSize; // size of the edges-cache in bytes +} astar_room; + +/**************************************************************************************************************/ + +typedef struct astar_nopath +{ + V2 S; + V2 E; + ::std::chrono::system_clock::time_point Tick; +} astar_nopath; + +/**************************************************************************************************************/ + +typedef struct astar +{ + ::std::thread* Thread; // the thread using this struct instance + ::std::atomic IsRunning; // true if the worker is working + ::std::atomic IsPaused; // true if the worker is paused + ::std::atomic DoPause; // flip to true to pause this worker (may take some time) + astar_node_data* NodesData; // the nodes-data memory used by this worker + int NodesDataSize; // sie of nodesdata in bytes + astar_node_meta** Grid; // the meta-grid used by this worker + astar_node* StartNode; // start-node of a path-calculation + astar_node* EndNode; // end-node of a path-calculation + astar_node* LastNode; // holds the last-node on a path (null if unreachable) + int ObjectID; // object id we're calculating a path for + int HeapSize; // current size of the open-heap + astar_room* Room; // the room we calculate a path in + astar_rooms* Rooms; // all room intances this worker is using + astar_command_queue* Commands; // holds pending commands supposed to be processed by worker + ::std::atomic Memory; +} astar; + +/**************************************************************************************************************/ +/* WORKER AND QUEUE INSTANCES */ +/**************************************************************************************************************/ + +extern astar AStar[THREADSCOUNT]; // the astar-workers +extern astar_query_queue AStarQueries; // the query-queue to send tasks to the workers +extern astar_response_queue AStarResponses; // the response-queue to retrieve answers from workers + +/**************************************************************************************************************/ +/* C FUNCTIONS */ +/**************************************************************************************************************/ + +void AStarInit(); +void AStarShutdown(); +bool AStarDeliverNextResponse(); +void AStarEnqueueCommand(astar_command* Command); +void AStarInitGC(); +void AStarFinishGC(); +void AStarPerformGC(); + +__inline void* AStarAlloc(astar* Astar, unsigned int Size) +{ + void* mem = malloc(Size); + Astar->Memory += Size; + return mem; +} + +__inline void AStarFree(astar* Astar, void* Mem, unsigned int Size) +{ + free(Mem); + Astar->Memory -= Size; +} + +#endif /*#ifndef _ASTAR_H */ \ No newline at end of file diff --git a/blakserv/astar_classes.h b/blakserv/astar_classes.h new file mode 100644 index 0000000000..61924ba331 --- /dev/null +++ b/blakserv/astar_classes.h @@ -0,0 +1,359 @@ +// Meridian 59, Copyright 1994-2012 Andrew Kirmse and Chris Kirmse. +// All rights reserved. +// +// This software is distributed under a license that is described in +// the LICENSE file that accompanies it. +// +// Meridian is a registered trademark. +/* +* astar_classes.h: Classes used by astar, mainly for the queue items. +* +*/ + +#ifndef _ASTAR_CLASSES__H +#define _ASTAR_CLASSES__H + +#include +#include + +/*********************************************************************************************************************/ + +typedef struct astar_node astar_node; +typedef struct astar_path_node astar_path_node; +typedef struct astar_room astar_room; + +/*********************************************************************************************************************/ + +class astar_path : public ::std::list { }; + +/*********************************************************************************************************************/ + +class astar_rooms : public ::std::map { }; + +/*********************************************************************************************************************/ +/* ENUMS */ +/*********************************************************************************************************************/ + +typedef enum astar_command_type +{ + AStarLoadRoom = 1, + AStarUnloadRoom = 2, + AStarBlockerAdd = 3, + AStarBlockerRemove = 4, + AStarBlockerMove = 5, + AStarBlockerClear = 6, + AStarMoveSector = 7, + AStarChangeTexture = 8 +} astar_command_type; + +typedef enum astar_query_type +{ + AStarPathQuery = 1 +} astar_query_type; + +typedef enum astar_response_type +{ + AStarPathResponse = 1 +} astar_response_type; + +/*********************************************************************************************************************/ +/* COMMANDS */ +/*********************************************************************************************************************/ + +class astar_command +{ +public: + virtual astar_command_type commandType() = 0; + virtual astar_command* clone() = 0; +}; + +/*********************************************************************************************************************/ + +class astar_command_loadroom : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarLoadRoom; } + + char* file; + int id; + + astar_command_loadroom(char* file, int id) + { + astar_command_loadroom::file = strdup(file); + astar_command_loadroom::id = id; + } + + ~astar_command_loadroom() + { + free(file); + } + + virtual astar_command* clone() override + { + return new astar_command_loadroom(file, id); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_unloadroom : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarUnloadRoom; } + + int id; + + astar_command_unloadroom(int id) + { + astar_command_unloadroom::id = id; + } + + virtual astar_command* clone() override + { + return new astar_command_unloadroom(id); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_blockeradd : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarBlockerAdd; } + + int roomid; + int objectid; + V2 p; + + astar_command_blockeradd(int roomid, int objectid, V2 p) + { + astar_command_blockeradd::roomid = roomid; + astar_command_blockeradd::objectid = objectid; + astar_command_blockeradd::p = p; + } + + virtual astar_command* clone() override + { + return new astar_command_blockeradd(roomid, objectid, p); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_blockerremove : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarBlockerRemove; } + + int roomid; + int objectid; + + astar_command_blockerremove(int roomid, int objectid) + { + astar_command_blockerremove::roomid = roomid; + astar_command_blockerremove::objectid = objectid; + } + + virtual astar_command* clone() override + { + return new astar_command_blockerremove(roomid, objectid); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_blockermove : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarBlockerMove; } + + int roomid; + int objectid; + V2 p; + + astar_command_blockermove(int roomid, int objectid, V2 p) + { + astar_command_blockermove::roomid = roomid; + astar_command_blockermove::objectid = objectid; + astar_command_blockermove::p = p; + } + + virtual astar_command* clone() override + { + return new astar_command_blockermove(roomid, objectid, p); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_blockerclear : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarBlockerClear; } + + int roomid; + + astar_command_blockerclear(int roomid) + { + astar_command_blockerclear::roomid = roomid; + } + + virtual astar_command* clone() override + { + return new astar_command_blockerclear(roomid); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_movesector : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarMoveSector; } + + int roomid; + unsigned int serverid; + bool floor; + float height; + float speed; + + astar_command_movesector(int roomid, unsigned int serverid, bool floor, float height, float speed) + { + astar_command_movesector::roomid = roomid; + astar_command_movesector::serverid = serverid; + astar_command_movesector::floor = floor; + astar_command_movesector::height = height; + astar_command_movesector::speed = speed; + } + + virtual astar_command* clone() override + { + return new astar_command_movesector(roomid, serverid, floor, height, speed); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_changetexture : public astar_command +{ +public: + virtual astar_command_type commandType() override { return astar_command_type::AStarChangeTexture; } + + int roomid; + unsigned int serverid; + unsigned short newtexture; + unsigned int flags; + + astar_command_changetexture(int roomid, unsigned int serverid, unsigned short newtexture, unsigned int flags) + { + astar_command_changetexture::roomid = roomid; + astar_command_changetexture::serverid = serverid; + astar_command_changetexture::newtexture = newtexture; + astar_command_changetexture::flags = flags; + } + + virtual astar_command* clone() override + { + return new astar_command_changetexture(roomid, serverid, newtexture, flags); + } +}; + +/*********************************************************************************************************************/ + +class astar_command_queue : public threadsafe_queue +{ +public: + astar_command_queue(int size) : threadsafe_queue(size) + { + } +}; + +/*********************************************************************************************************************/ +/* QUERIES */ +/*********************************************************************************************************************/ + +class astar_query +{ +public: + virtual astar_query_type queryType() = 0; +}; + + +/*********************************************************************************************************************/ + +class astar_pathquery : public astar_query +{ +public: + virtual astar_query_type queryType() override { return astar_query_type::AStarPathQuery; } + + int roomid; + int objectid; + V2 s; + V2 e; + unsigned int flags; + + astar_pathquery(int roomid, int objectid, V2 s, V2 e, unsigned int flags) + { + astar_pathquery::roomid = roomid; + astar_pathquery::objectid = objectid; + astar_pathquery::s = s; + astar_pathquery::e = e; + astar_pathquery::flags = flags; + } +}; + +/*********************************************************************************************************************/ + +class astar_query_queue : public threadsafe_queue +{ +public: + astar_query_queue(int size) : threadsafe_queue(size) + { + } +}; + +/*********************************************************************************************************************/ +/* RESPONSES */ +/*********************************************************************************************************************/ + +class astar_response +{ +public: + virtual astar_response_type responseType() = 0; +}; + +/*********************************************************************************************************************/ + +class astar_pathresponse : public astar_response +{ +public: + virtual astar_response_type responseType() override { return astar_response_type::AStarPathResponse; } + + int roomid; + int objectid; + V2 s; + V2 e; + unsigned int flags; + astar_path path; + + astar_pathresponse(int roomid, int objectid, V2 s, V2 e, unsigned int flags) + { + astar_pathresponse::roomid = roomid; + astar_pathresponse::objectid = objectid; + astar_pathresponse::s = s; + astar_pathresponse::e = e; + astar_pathresponse::flags = flags; + } +}; + +/*********************************************************************************************************************/ + +class astar_response_queue : public threadsafe_queue +{ +public: + astar_response_queue(int size) : threadsafe_queue(size) + { + } +}; + +#endif /*#ifndef _ASTAR_CLASSES__H */ \ No newline at end of file diff --git a/blakserv/blakserv.h b/blakserv/blakserv.h index b955736ff7..20cf5c8911 100644 --- a/blakserv/blakserv.h +++ b/blakserv/blakserv.h @@ -78,7 +78,14 @@ enum REALTIME_CLASS = 36, EVENTENGINE_CLASS = 37, ESCAPED_CONVICT_CLASS = 38, - MAX_BUILTIN_CLASS = 38 + MOVECALLBACK_MSG = 39, + IROW_PARM = 40, + ICOL_PARM = 41, + IFINEROW_PARM = 42, + IFINECOL_PARM = 43, + ITYPE_PARM = 44, + IFLAGS_PARM = 45, + MAX_BUILTIN_CLASS = 45 // To add other C-accessible KOD identifiers, // see the BLAKCOMP's table of BuiltinIds[]. @@ -234,6 +241,7 @@ char * GetLastErrorStr(); #define WM_BLAK_MAIN_DELETE_ACCOUNT (WM_APP + 4002) #define WM_BLAK_MAIN_VERIFIED_LOGIN (WM_APP + 4003) #define WM_BLAK_MAIN_LOAD_GAME (WM_APP + 4004) +#define WM_BLAK_MAIN_PATH_READY (WM_APP + 4005) #include "bof.h" @@ -242,6 +250,9 @@ char * GetLastErrorStr(); #include "stringinthash.h" #include "intstringhash.h" +#include "queue.h" +#include "geometry.h" + #include "blakres.h" #include "channel.h" #include "kodbase.h" @@ -258,6 +269,8 @@ char * GetLastErrorStr(); #include "system.h" #include "loadrsc.h" #include "loadgame.h" +#include "astar_classes.h" +#include "astar.h" #include "roofile.h" #include "roomdata.h" #include "files.h" diff --git a/blakserv/blakserv.vcxproj b/blakserv/blakserv.vcxproj index 0cab30be47..3251df85f1 100644 --- a/blakserv/blakserv.vcxproj +++ b/blakserv/blakserv.vcxproj @@ -191,6 +191,8 @@ copy $(SolutionDir)bin\libcurl.dll $(SolutionDir)run\server + + @@ -232,6 +234,7 @@ copy $(SolutionDir)bin\libcurl.dll $(SolutionDir)run\server + @@ -265,6 +268,7 @@ copy $(SolutionDir)bin\libcurl.dll $(SolutionDir)run\server + diff --git a/blakserv/blakserv.vcxproj.filters b/blakserv/blakserv.vcxproj.filters index 4da1eda561..d90611228e 100644 --- a/blakserv/blakserv.vcxproj.filters +++ b/blakserv/blakserv.vcxproj.filters @@ -230,6 +230,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -436,6 +445,9 @@ Source Files + + Source Files + diff --git a/blakserv/ccode.c b/blakserv/ccode.c index aa97f97305..8b76855b55 100644 --- a/blakserv/ccode.c +++ b/blakserv/ccode.c @@ -2693,7 +2693,7 @@ int C_CanMoveInRoomBSP(int object_id, local_var_type *local_vars, e.Y = GRIDCOORDTOROO(row_dest.v.data, finerow_dest.v.data); Wall* blockWall; - ret_val.v.data = BSPCanMoveInRoom(&r->data, &s, &e, objectid.v.data, (move_outside_bsp.v.data != 0), &blockWall); + ret_val.v.data = BSPCanMoveInRoom(&r->data, &s, &e, objectid.v.data, (move_outside_bsp.v.data != 0), &blockWall, false); #if DEBUGMOVE //dprintf("MOVE:%i R:%i S:(%1.2f/%1.2f) E:(%1.2f/%1.2f)", ret_val.v.data, r->data.roomdata_id, s.X, s.Y, e.X, e.Y); @@ -2889,9 +2889,13 @@ int C_ChangeTextureBSP(int object_id, local_var_type *local_vars, return ret_val.int_val; } - BSPChangeTexture(&r->data, (unsigned short)server_id.v.data, + BSPChangeTexture(&r->data, (unsigned int)server_id.v.data, (unsigned short)new_texnum.v.data, flags.v.data); + // also change texture in astar workers + AStarEnqueueCommand( + new astar_command_changetexture(r->data.roomdata_id, (unsigned int)server_id.v.data, (unsigned short)new_texnum.v.data, (unsigned int)flags.v.data)); + return ret_val.int_val; } @@ -2964,6 +2968,10 @@ int C_MoveSectorBSP(int object_id, local_var_type *local_vars, BSPMoveSector(&r->data, (unsigned int)server_id.v.data, is_floor, fheight, fspeed); + // also move sector in astar workers + AStarEnqueueCommand( + new astar_command_movesector(r->data.roomdata_id, (unsigned int)server_id.v.data, is_floor, fheight, fspeed)); + return ret_val.int_val; } @@ -3047,6 +3055,9 @@ int C_BlockerAddBSP(int object_id, local_var_type *local_vars, // query ret_val.v.data = BSPBlockerAdd(&r->data, obj_val.v.data, &p); + // also add to astar workers + AStarEnqueueCommand(new astar_command_blockeradd(r->data.roomdata_id, obj_val.v.data, p)); + return ret_val.int_val; } @@ -3130,6 +3141,9 @@ int C_BlockerMoveBSP(int object_id, local_var_type *local_vars, // query ret_val.v.data = BSPBlockerMove(&r->data, obj_val.v.data, &p); + // also move in astar workers + AStarEnqueueCommand(new astar_command_blockermove(r->data.roomdata_id, obj_val.v.data, p)); + return ret_val.int_val; } @@ -3172,6 +3186,9 @@ int C_BlockerRemoveBSP(int object_id, local_var_type *local_vars, // query ret_val.v.data = BSPBlockerRemove(&r->data, obj_val.v.data); + // also remove in astar workers + AStarEnqueueCommand(new astar_command_blockerremove(r->data.roomdata_id, obj_val.v.data)); + return ret_val.int_val; } @@ -3205,6 +3222,9 @@ int C_BlockerClearBSP(int object_id, local_var_type *local_vars, // query BSPBlockerClear(&r->data); + // also clear in astar workers + AStarEnqueueCommand(new astar_command_blockerclear(r->data.roomdata_id)); + return ret_val.int_val; } @@ -3436,31 +3456,14 @@ int C_GetStepTowardsBSP(int object_id, local_var_type *local_vars, s.Y = GRIDCOORDTOROO(row_source.v.data, finerow_source.v.data); V2 e; - e.X = GRIDCOORDTOROO(local_vars->locals[col_dest.v.data].v.data, local_vars->locals[finecol_dest.v.data].v.data); - e.Y = GRIDCOORDTOROO(local_vars->locals[row_dest.v.data].v.data, local_vars->locals[finerow_dest.v.data].v.data); + e.X = GRIDCOORDTOROO(col_dest.v.data, finecol_dest.v.data); + e.Y = GRIDCOORDTOROO(row_dest.v.data, finerow_dest.v.data); - V2 p; unsigned int flags = (unsigned int)state_flags.v.data; - bool ok = BSPGetStepTowards(&r->data, &s, &e, &p, &flags, objectid.v.data); - - if (ok) - { - ret_val.v.tag = TAG_INT; - ret_val.v.data = flags; - - local_vars->locals[finecol_dest.v.data].v.tag = TAG_INT; - local_vars->locals[finecol_dest.v.data].v.data = ROOCOORDTOGRIDFINE(p.X); - - local_vars->locals[finerow_dest.v.data].v.tag = TAG_INT; - local_vars->locals[finerow_dest.v.data].v.data = ROOCOORDTOGRIDFINE(p.Y); - - local_vars->locals[col_dest.v.data].v.tag = TAG_INT; - local_vars->locals[col_dest.v.data].v.data = ROOCOORDTOGRIDBIG(p.X); - - local_vars->locals[row_dest.v.data].v.tag = TAG_INT; - local_vars->locals[row_dest.v.data].v.data = ROOCOORDTOGRIDBIG(p.Y); - } + BSPGetStepTowards(&r->data, &s, &e, flags, objectid.v.data); + ret_val.v.tag = TAG_INT; + ret_val.v.data = 1; return ret_val.int_val; } diff --git a/blakserv/garbage.c b/blakserv/garbage.c index 3ce4061974..8252c329e9 100644 --- a/blakserv/garbage.c +++ b/blakserv/garbage.c @@ -96,10 +96,14 @@ int next_table_renumber; void GarbageCollect() { + /* anyone in game mode w/o a user can have stale data, so knock 'em out */ ForEachSession(GarbageKickoffGamePick); ForEachSession(GarbageWarnAdminSession); + + // wait for astar workers to pause + AStarInitGC(); UpdateSecurityRedbook(); @@ -113,6 +117,8 @@ void GarbageCollect() // Tables now get GC'd, so don't reset them. //ResetTables(); + + /* First, garbage collect the list nodes and tables */ /* @@ -205,6 +211,8 @@ void GarbageCollect() ForEachObject(RenumberObject); // Renumber object IDs in each room's blocker data. ForEachRoom(RenumberBlockerObjects); + // Renumber object IDs in astar + AStarPerformGC(); ForEachObject(RenumberObjectReferences); // Also mark strings here ForEachListNode(RenumberListNodeObjectReferences); // Also mark strings here ForEachTable(RenumberTableObjectReferences); // Also mark strings here @@ -236,6 +244,9 @@ void GarbageCollect() SetNumTimers(next_timer_renumber); ForEachString(CompactString); SetNumStrings(next_string_renumber); + + // continue all astar workers + AStarFinishGC(); } ///////////////////////////////////////////////////////////////////////////// diff --git a/blakserv/main.c b/blakserv/main.c index eb3744b9b0..a5aae0c500 100644 --- a/blakserv/main.c +++ b/blakserv/main.c @@ -83,6 +83,7 @@ void MainServer() InitTimer(); InitSession(); InitResource(); + AStarInit(); InitRooms(); InitString(); InitUser(); @@ -158,6 +159,7 @@ void MainExitServer() ResetString(); // ExitRooms calls ResetRooms in addition to clearing the array memory. ExitRooms(); + AStarShutdown(); ResetResource(); ResetTimer(); ResetList(); diff --git a/blakserv/makefile b/blakserv/makefile index 4724348a27..00776bf6f3 100644 --- a/blakserv/makefile +++ b/blakserv/makefile @@ -91,7 +91,8 @@ OBJS = \ $(OUTDIR)\files.obj \ $(OUTDIR)\sprocket.obj \ $(OUTDIR)\database.obj \ - + $(OUTDIR)\astar.obj \ + all : makedirs $(OUTDIR)\blakserv.exe $(OUTDIR)\rscload.obj : $(TOPDIR)\util\rscload.c diff --git a/blakserv/memory.c b/blakserv/memory.c index dc7439ea9b..1b56c56825 100644 --- a/blakserv/memory.c +++ b/blakserv/memory.c @@ -247,7 +247,7 @@ const char *memory_stat_names[] = "Systimer", "Nameid", "Class", "Message", "Object", "List", "Object properties", - "Configuration", "Rooms", + "Configuration", "Rooms", "Astar", "Admin constants", "Buffers", "Game loading", "Tables", "Socket blocks", "Game saving", @@ -275,6 +275,11 @@ void InitMemory(void) memory_statistics * GetMemoryStats(void) { + // update current value from astar first + memory_stat.allocated[MALLOC_ID_ASTAR] = 0; + for (int i = 0; i < THREADSCOUNT; i++) + memory_stat.allocated[MALLOC_ID_ASTAR] += AStar[i].Memory; + return &memory_stat; } @@ -282,6 +287,11 @@ int GetMemoryTotal(void) { int i,total; + // update current value from astar first + memory_stat.allocated[MALLOC_ID_ASTAR] = 0; + for (int i = 0; i < THREADSCOUNT; i++) + memory_stat.allocated[MALLOC_ID_ASTAR] += AStar[i].Memory; + total = 0; for (i=0;i +#include + +template +class threadsafe_queue +{ +private: + ::std::queue queue; + ::std::mutex mutex; + unsigned int maxsize; + +public: + threadsafe_queue(int size) + { + maxsize = size; + } + + ~threadsafe_queue() + { + } + + bool enqueue(T item) + { + bool ret = false; + + mutex.lock(); + if (queue.size() < maxsize) + { + ret = true; + queue.push(item); + } + mutex.unlock(); + + return ret; + } + + T dequeue() + { + T ret = NULL; + + mutex.lock(); + if (!queue.empty()) + { + ret = queue.front(); + queue.pop(); + } + mutex.unlock(); + + return ret; + } + + bool empty() + { + bool ret = true; + + mutex.lock(); + ret = queue.empty(); + mutex.unlock(); + + return ret; + } +}; +#endif /*#ifndef _QUEUE_H */ diff --git a/blakserv/roofile.c b/blakserv/roofile.c index de9179905f..3dacf25811 100644 --- a/blakserv/roofile.c +++ b/blakserv/roofile.c @@ -598,6 +598,35 @@ bool BSPCanMoveInRoomTree(BspNode* Node, V2* S, V2* E, Wall** BlockWall) return BSPCanMoveInRoomTree(Node->u.internal.LeftChild, S, E, BlockWall); } } + +void BSPClearPath(astar_path* Path) +{ + while (!Path->empty()) + { + astar_path_node* pathnode = Path->front(); + Path->pop_front(); + free(pathnode); + } +} + +void BSPClearNoPath(astar_nopath* NoPath) +{ + NoPath->S = { 0.0f, 0.0f }; + NoPath->E = { 0.0f, 0.0f }; + NoPath->Tick = ::std::chrono::time_point<::std::chrono::high_resolution_clock, ::std::chrono::milliseconds>(); +} + +void BSPClearPathCache(room_type* Room) +{ + for (int i = 0; i < PATHCACHESIZE; i++) + BSPClearPath(Room->Paths[i]); +} + +void BSPClearNoPathCache(room_type* Room) +{ + for (int i = 0; i < NOPATHCACHESIZE; i++) + BSPClearNoPath(Room->NoPaths[i]); +} #pragma endregion #pragma region Public @@ -606,6 +635,102 @@ bool BSPCanMoveInRoomTree(BspNode* Node, V2* S, V2* E, Wall** BlockWall) /* These are defined in header and can be called from outside */ /**************************************************************************************************************/ +bool BSPGetStepFromPath(room_type* Room, astar_path* Path, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID) +{ + V2 d; + Wall* blockWall; + + // a valid path would have at least two entries + if (Path->size() < 2) + return false; + + // check start (better match almost exactly, or we might stepping back?) + astar_path_node* first = Path->front(); + V2SUB(&d, &first->Location, S); + if (V2LEN2(&d) > PATHCACHESTARTTOLERANCE*PATHCACHESTARTTOLERANCE) + return false; + + // check end (has small tolerance) + astar_path_node* last = Path->back(); + V2SUB(&d, &last->Location, E); + if (V2LEN2(&d) > PATHCACHEENDTOLERANCE*PATHCACHEENDTOLERANCE) + return false; + + // match! now remove first, so we also match on next step + Path->pop_front(); + + // get the next step endpoint + astar_path_node* next = Path->front(); + + // make sure we can still move to this next endpoint (cares for moved objects!) + // note: if objects block a cached path, the path will still be walked until the block occurs! + // to improve this revalidate the whole path from the first to the last node here + if (!BSPCanMoveInRoom(Room, &first->Location, &next->Location, ObjectID, false, &blockWall, false)) + return false; + + // for diagonal moves mark to be long step (required for timer elapse) + if (abs(first->Col - next->Col) && + abs(first->Row - next->Row)) + { + *Flags |= MF_LONG_STEP; + } + else + *Flags &= ~MF_LONG_STEP; + + // set step endpoint + *P = next->Location; + + // delete removed entry + free(first); + + // cache hit + return true; +} + +bool BSPGetStepFromPathCache(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID) +{ + for (int i = 0; i < PATHCACHESIZE; i++) + if (BSPGetStepFromPath(Room, Room->Paths[i], S, E, P, Flags, ObjectID)) + return true; + + // no cache hit + return false; +} + +bool BSPGetStepFromNoPath(astar_nopath* NoPath, V2* S, V2* E) +{ + V2 d; + + // must match start + V2SUB(&d, &NoPath->S, S); + if (V2LEN2(&d) > NOPATHCACHESTARTTOLERANCE*NOPATHCACHESTARTTOLERANCE) + return false; + + // must match end + V2SUB(&d, &NoPath->E, E); + if (V2LEN2(&d) > NOPATHCACHEENDTOLERANCE*NOPATHCACHEENDTOLERANCE) + return false; + + // must not be too long ago + ::std::chrono::system_clock::time_point now = + ::std::chrono::high_resolution_clock::now(); + + if (now - NoPath->Tick > ::std::chrono::seconds(NOPATHCACHEVALIDDURATIONINS)) + return false; + + return true; +} + +bool BSPGetStepFromNoPathCache(room_type* Room, V2* S, V2* E) +{ + for (int i = 0; i < NOPATHCACHESIZE; i++) + if (BSPGetStepFromNoPath(Room->NoPaths[i], S, E)) + return true; + + // no cache hit + return false; +} + /*********************************************************************************************/ /* BSPGetHeight: Returns true if location is inside any sector, false otherwise. /* If true, heights are in parameters HeightF (floor), @@ -674,7 +799,7 @@ bool BSPLineOfSight(room_type* Room, V3* S, V3* E) /*********************************************************************************************/ /* BSPCanMoveInRoom: Checks if you can walk a straight line from (S)tart to (E)nd */ /*********************************************************************************************/ -bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOutsideBSP, Wall** BlockWall) +bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOutsideBSP, Wall** BlockWall, bool SkipBlockers) { if (!Room || Room->TreeNodesCount == 0 || !S || !E) return false; @@ -695,6 +820,10 @@ bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOuts if (!roomok) return false; + // we're done if no need to check object blockers + if (SkipBlockers) + return true; + // otherwise also check against blockers Blocker* blocker = Room->Blocker; while (blocker) @@ -709,7 +838,7 @@ bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOuts V2 ms; // from m to s V2SUB(&ms, S, &blocker->Position); float ds2 = V2LEN2(&ms); - + // CASE 1) Start is too close // Note: IntersectLineCircle below will reject moves starting or ending exactly // on the circle as well as moves going from inside to outside of the circle. @@ -794,6 +923,10 @@ void BSPChangeTexture(room_type* Room, unsigned int ServerID, unsigned short New sector->CeilingTexture = (isReset ? sector->CeilingTextureOrig : NewTexture); } } + + // must invalidate astar pathcache attached to room + BSPClearPathCache(Room); + BSPClearNoPathCache(Room); } /*********************************************************************************************/ @@ -824,6 +957,10 @@ void BSPMoveSector(room_type* Room, unsigned int ServerID, bool Floor, float Hei BSPUpdateLeafHeights(Room, sector, false); } } + + // must invalidate astar pathcache attached to room + BSPClearPathCache(Room); + BSPClearNoPathCache(Room); } /*********************************************************************************************/ @@ -966,22 +1103,64 @@ bool BSPGetRandomPoint(room_type* Room, int MaxAttempts, V2* P) } /*********************************************************************************************/ -/* BSPGetStepTowards: Returns a location in P param, in a distant of 16 kod fineness units -/* away from S on the way towards E. +/* BSPInvokeMoveCallback: Delivers a next-step response to the requesting KOD object /*********************************************************************************************/ -bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID) +void BSPInvokeMoveCallback(int ObjectID, int Type, unsigned int Flags, V2* P) { - if (!Room || !S || !E || !P || !Flags) - return false; + val_type vals[6]; + vals[0].v.tag = TAG_INT; + vals[0].v.data = Type; + vals[1].v.tag = TAG_INT; + vals[1].v.data = Flags; + vals[2].v.tag = TAG_INT; + vals[2].v.data = ROOCOORDTOGRIDBIG(P->Y); + vals[3].v.tag = TAG_INT; + vals[3].v.data = ROOCOORDTOGRIDBIG(P->X); + vals[4].v.tag = TAG_INT; + vals[4].v.data = ROOCOORDTOGRIDFINE(P->Y); + vals[5].v.tag = TAG_INT; + vals[5].v.data = ROOCOORDTOGRIDFINE(P->X); + + parm_node parms[6]; + parms[0].type = CONSTANT; + parms[0].name_id = ITYPE_PARM; + parms[0].value = vals[0].int_val; + + parms[1].type = CONSTANT; + parms[1].name_id = IFLAGS_PARM; + parms[1].value = vals[1].int_val; + + parms[2].type = CONSTANT; + parms[2].name_id = IROW_PARM; + parms[2].value = vals[2].int_val; + + parms[3].name_id = ICOL_PARM; + parms[3].type = CONSTANT; + parms[3].value = vals[3].int_val; + + parms[4].name_id = IFINEROW_PARM; + parms[4].type = CONSTANT; + parms[4].value = vals[4].int_val; + + parms[5].name_id = IFINECOL_PARM; + parms[5].type = CONSTANT; + parms[5].value = vals[5].int_val; + + SendBlakodMessage(ObjectID, MOVECALLBACK_MSG, 6, parms); +} - // Monsters that can move through walls or outside the tree will - // send this flag with state. - bool moveOutsideBSP = ((*Flags & MSTATE_MOVE_OUTSIDE_BSP) == MSTATE_MOVE_OUTSIDE_BSP); +/*********************************************************************************************/ +/* BSPGetStepFromHeuristic: Determines a new point to step to by an heuristic. +/*********************************************************************************************/ +bool BSPGetStepFromHeuristic(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID) +{ + Wall* blockWall = NULL; + V2 se, stepend; - // but must not give these back in piState - *Flags &= ~MSTATE_MOVE_OUTSIDE_BSP; + bool isAvoiding = ((*Flags & MF_AVOIDING) == MF_AVOIDING); + bool isLeft = ((*Flags & MF_CLOCKWISE) == MF_CLOCKWISE); - V2 se, stepend; + // delta vector from start to end V2SUB(&se, E, S); // get length from start to end @@ -990,10 +1169,9 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags // trying to step to old location? if (ISZERO(len)) { - // set step destination to end - *P = *E; - *Flags &= ~ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *P = *S; + *Flags &= ~MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } @@ -1004,32 +1182,22 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags // apply the scale on se V2SCALE(&se, scale); - /****************************************************/ - // 1) try direct step towards destination first - /****************************************************/ - Wall* blockWall = NULL; + /**********************************************************************************/ + // first try a step straight towards destination - // note: we must verify the location the object is actually going to end up in KOD, - // this means we must round to the next closer kod-fineness value, - // so these values are also exactly expressable in kod coordinates. - // in fact this makes the vector a variable length between ~15.5 and ~16.5 fine units V2ADD(&stepend, S, &se); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags &= ~ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags &= ~MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } - - /****************************************************/ - // 2) can't do direct step - /****************************************************/ - bool isAvoiding = ((*Flags & ESTATE_AVOIDING) == ESTATE_AVOIDING); - bool isLeft = ((*Flags & ESTATE_CLOCKWISE) == ESTATE_CLOCKWISE); + /**********************************************************************************/ + // determine step by heuristic // not yet in clockwise or cclockwise mode if (!isAvoiding) @@ -1064,16 +1232,13 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags bool q2_neg = (df <= (float)-M_PI_2 && df >= (float)-M_PI); bool q3_neg = (df <= (float)-M_PI && df >= (float)-(M_PI + M_PI_2)); bool q4_neg = (df <= (float)-(M_PI + M_PI_2) && df >= (float)-M_PI*2.0f); - - isLeft = (q1_pos || q2_pos || q1_neg || q3_neg) ? false : true; - /*if (isLeft) - dprintf("trying left first r: %f", df); - else - dprintf("trying right first r: %f", df);*/ + isLeft = (q1_pos || q2_pos || q1_neg || q3_neg) ? false : true; } } + /**********************************************************************************/ + // must run this possibly twice // e.g. left after right failed or right after left failed for (int i = 0; i < 2; i++) @@ -1082,16 +1247,16 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags { V2 v = se; - // try 22.5° left - V2ROTATE(&v, 0.5f * (float)-M_PI_4); - V2ADD(&stepend, S, &v); + // try 22.5° left + V2ROTATE(&v, 0.5f * (float)-M_PI_4); + V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) - { + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) + { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; } @@ -1100,11 +1265,11 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; } @@ -1113,24 +1278,24 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; - } + } // try 90° left V2ROTATE(&v, 0.5f * (float)-M_PI_4); V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; } @@ -1139,11 +1304,11 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; } @@ -1152,18 +1317,18 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; return true; } // failed to circumvent by going left, switch to right isLeft = false; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; } else { @@ -1174,11 +1339,11 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } @@ -1187,11 +1352,11 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } @@ -1200,24 +1365,24 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; - } + } // try 90° right V2ROTATE(&v, 0.5f * (float)M_PI_4); V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } @@ -1226,11 +1391,11 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } @@ -1239,29 +1404,123 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2ADD(&stepend, S, &v); stepend.X = ROUNDROOTOKODFINENESS(stepend.X); stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); - if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall)) + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, false, &blockWall, false)) { *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags &= ~MF_CLOCKWISE; return true; } // failed to circumvent by going right, switch to left isLeft = true; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; + *Flags |= MF_AVOIDING; + *Flags |= MF_CLOCKWISE; } } + return false; +} + +/*********************************************************************************************/ +/* BSPGetStepTowards: +/*********************************************************************************************/ +void BSPGetStepTowards(room_type* Room, V2* S, V2* E, unsigned int Flags, int ObjectID) +{ + Wall* blockWall = NULL; + + if (!Room || !S || !E) + return; + + // some monsters can move through walls or outside the tree + bool moveOutsideBSP = ((Flags & MF_MOVE_OUTSIDE_BSP) == MF_MOVE_OUTSIDE_BSP); + + V2 se, stepend; + V2SUB(&se, E, S); + + // get length from start to end + float len = V2LEN(&se); + + // trying to step to old location? + if (ISZERO(len)) + { + BSPInvokeMoveCallback(ObjectID, MC_STRAIGHTLINE, Flags, S); + return; + } + + // this first normalizes the se vector, + // then scales to a length of 16 kod-fineunits (=256 roo-fineunits) + float scale = (1.0f / len) * FINENESSKODTOROO(16.0f); + + // apply the scale on se + V2SCALE(&se, scale); /****************************************************/ - // 3) fully stuck + // 1) try step on straight line towards destination + // Note: Blockers are not important on the long line, only short /****************************************************/ + if (BSPCanMoveInRoom(Room, S, E, ObjectID, moveOutsideBSP, &blockWall, true)) + { + // note: we must verify the location the object is actually going to end up in KOD, + // this means we must round to the next closer kod-fineness value, + // so these values are also exactly expressable in kod coordinates. + // in fact this makes the vector a variable length between ~15.5 and ~16.5 fine units + V2ADD(&stepend, S, &se); + stepend.X = ROUNDROOTOKODFINENESS(stepend.X); + stepend.Y = ROUNDROOTOKODFINENESS(stepend.Y); + if (BSPCanMoveInRoom(Room, S, &stepend, ObjectID, moveOutsideBSP, &blockWall, false)) + { + BSPInvokeMoveCallback(ObjectID, MC_STRAIGHTLINE, Flags, &stepend); + return; + } + } - *P = *S; - *Flags &= ~ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return false; + /****************************************************/ + // 2) step from path cache + /****************************************************/ + if (BSPGetStepFromPathCache(Room, S, E, &stepend, &Flags, ObjectID)) + { + BSPInvokeMoveCallback(ObjectID, MC_PATHFROMCACHE, Flags, &stepend); + return; + } + + /****************************************************/ + // 3) get step from nopath (unreachable) cache + /****************************************************/ + if (BSPGetStepFromNoPathCache(Room, S, E)) + { + // step by heuristic + if (BSPGetStepFromHeuristic(Room, S, E, &stepend, &Flags, ObjectID)) + { + BSPInvokeMoveCallback(ObjectID, MC_HEURISTIC, Flags, &stepend); + return; + } + + // full block + else + { + stepend = { 0.0f, 0.0f }; + BSPInvokeMoveCallback(ObjectID, MC_UNREACHABLE, Flags, &stepend); + return; + } + } + + /****************************************************/ + // 4) calculate new path async + /****************************************************/ + astar_pathquery* query = new astar_pathquery(Room->roomdata_id, ObjectID, *S, *E, Flags); + if (!AStarQueries.enqueue(query)) + { + // failed because full, fall back to heuristic + if (BSPGetStepFromHeuristic(Room, S, E, &stepend, &Flags, ObjectID)) + BSPInvokeMoveCallback(ObjectID, MC_HEURISTIC, Flags, &stepend); + + // full block + else + { + stepend = { 0.0f, 0.0f }; + BSPInvokeMoveCallback(ObjectID, MC_UNREACHABLE, Flags, &stepend); + } + } } /*********************************************************************************************/ @@ -1273,7 +1532,7 @@ void BSPBlockerClear(room_type* Room) while (blocker) { Blocker* tmp = blocker->Next; - FreeMemory(MALLOC_ID_ROOM, blocker, sizeof(Blocker)); + free(blocker); blocker = tmp; } Room->Blocker = NULL; @@ -1303,7 +1562,7 @@ bool BSPBlockerRemove(room_type* Room, int ObjectID) previous->Next = blocker->Next; // now cleanup node - FreeMemory(MALLOC_ID_ROOM, blocker, sizeof(Blocker)); + free(blocker); return true; } @@ -1324,7 +1583,7 @@ bool BSPBlockerAdd(room_type* Room, int ObjectID, V2* P) return false; // alloc - Blocker* newblocker = (Blocker*)AllocateMemory(MALLOC_ID_ROOM, sizeof(Blocker)); + Blocker* newblocker = (Blocker*)malloc(sizeof(Blocker)); // set values on new blocker newblocker->ObjectID = ObjectID; @@ -1372,7 +1631,7 @@ bool BSPBlockerMove(room_type* Room, int ObjectID, V2* P) /*********************************************************************************************/ /* BSPRooFileLoadServer: Fill "room" with server-relevant data from given roo file. */ /*********************************************************************************************/ -bool BSPLoadRoom(char *fname, room_type *room) +bool BSPLoadRoom(char *fname, room_type *room, astar* Astar) { int i, j, temp; unsigned char byte; @@ -1454,8 +1713,9 @@ bool BSPLoadRoom(char *fname, room_type *room) { fclose(infile); return False; } // allocate tree mem - room->TreeNodes = (BspNode*)AllocateMemory( - MALLOC_ID_ROOM, room->TreeNodesCount * sizeof(BspNode)); + room->TreeNodes = (Astar) ? + (BspNode*)AStarAlloc(Astar, room->TreeNodesCount * sizeof(BspNode)) : + (BspNode*)AllocateMemory(MALLOC_ID_ROOM, room->TreeNodesCount * sizeof(BspNode)); for (i = 0; i < room->TreeNodesCount; i++) { @@ -1507,10 +1767,13 @@ bool BSPLoadRoom(char *fname, room_type *room) { fclose(infile); return False; } // allocate memory for points of polygon - node->u.leaf.PointsFloor = (V3*)AllocateMemory( - MALLOC_ID_ROOM, node->u.leaf.PointsCount * sizeof(V3)); - node->u.leaf.PointsCeiling = (V3*)AllocateMemory( - MALLOC_ID_ROOM, node->u.leaf.PointsCount * sizeof(V3)); + node->u.leaf.PointsFloor = (Astar) ? + (V3*)AStarAlloc(Astar, node->u.leaf.PointsCount * sizeof(V3)) : + (V3*)AllocateMemory(MALLOC_ID_ROOM, node->u.leaf.PointsCount * sizeof(V3)); + + node->u.leaf.PointsCeiling = (Astar) ? + (V3*)AStarAlloc(Astar, node->u.leaf.PointsCount * sizeof(V3)) : + (V3*)AllocateMemory(MALLOC_ID_ROOM, node->u.leaf.PointsCount * sizeof(V3)); // read points for (j = 0; j < node->u.leaf.PointsCount; j++) @@ -1536,8 +1799,9 @@ bool BSPLoadRoom(char *fname, room_type *room) { fclose(infile); return False; } // allocate walls mem - room->Walls = (Wall*)AllocateMemory( - MALLOC_ID_ROOM, room->WallsCount * sizeof(Wall)); + room->Walls = (Astar) ? + (Wall*)AStarAlloc(Astar, room->WallsCount * sizeof(Wall)) : + (Wall*)AllocateMemory(MALLOC_ID_ROOM, room->WallsCount * sizeof(Wall)); for (i = 0; i < room->WallsCount; i++) { @@ -1596,8 +1860,9 @@ bool BSPLoadRoom(char *fname, room_type *room) { fclose(infile); return False; } // allocate sides mem - room->Sides = (Side*)AllocateMemory( - MALLOC_ID_ROOM, room->SidesCount * sizeof(Side)); + room->Sides = (Astar) ? + (Side*)AStarAlloc(Astar, room->SidesCount * sizeof(Side)) : + (Side*)AllocateMemory(MALLOC_ID_ROOM, room->SidesCount * sizeof(Side)); for (i = 0; i < room->SidesCount; i++) { @@ -1638,8 +1903,9 @@ bool BSPLoadRoom(char *fname, room_type *room) { fclose(infile); return False; } // allocate sectors mem - room->Sectors = (Sector*)AllocateMemory( - MALLOC_ID_ROOM, room->SectorsCount * sizeof(Sector)); + room->Sectors = (Astar) ? + (Sector*)AStarAlloc(Astar, room->SectorsCount * sizeof(Sector)) : + (Sector*)AllocateMemory(MALLOC_ID_ROOM, room->SectorsCount * sizeof(Sector)); for (i = 0; i < room->SectorsCount; i++) { @@ -1688,8 +1954,9 @@ bool BSPLoadRoom(char *fname, room_type *room) // possibly load floor slopeinfo if ((sector->Flags & SF_SLOPED_FLOOR) == SF_SLOPED_FLOOR) { - sector->SlopeInfoFloor = (SlopeInfo*)AllocateMemory( - MALLOC_ID_ROOM, sizeof(SlopeInfo)); + sector->SlopeInfoFloor = (Astar) ? + (SlopeInfo*)AStarAlloc(Astar, sizeof(SlopeInfo)) : + (SlopeInfo*)AllocateMemory(MALLOC_ID_ROOM, sizeof(SlopeInfo)); // read 3d plane equation coefficients (normal vector) if (fread(§or->SlopeInfoFloor->A, 1, 4, infile) != 4) @@ -1719,8 +1986,9 @@ bool BSPLoadRoom(char *fname, room_type *room) // possibly load ceiling slopeinfo if ((sector->Flags & SF_SLOPED_CEILING) == SF_SLOPED_CEILING) { - sector->SlopeInfoCeiling = (SlopeInfo*)AllocateMemory( - MALLOC_ID_ROOM, sizeof(SlopeInfo)); + sector->SlopeInfoCeiling = (Astar) ? + (SlopeInfo*)AStarAlloc(Astar, sizeof(SlopeInfo)) : + (SlopeInfo*)AllocateMemory(MALLOC_ID_ROOM, sizeof(SlopeInfo)); // read 3d plane equation coefficients (normal vector) if (fread(§or->SlopeInfoCeiling->A, 1, 4, infile) != 4) @@ -1855,43 +2123,43 @@ bool BSPLoadRoom(char *fname, room_type *room) // bsp nodes for (int i = 0; i < room->TreeNodesCount; i++) { - BspNode* node = &room->TreeNodes[i]; - - // internal nodes - if (node->Type == BspInternalType) - { - // first wall - if (node->u.internal.FirstWallNum > 0 && - room->WallsCount > node->u.internal.FirstWallNum - 1) - node->u.internal.FirstWall = &room->Walls[node->u.internal.FirstWallNum - 1]; - else - node->u.internal.FirstWall = NULL; - - // right child - if (node->u.internal.RightChildNum > 0 && - room->TreeNodesCount > node->u.internal.RightChildNum - 1) - node->u.internal.RightChild = &room->TreeNodes[node->u.internal.RightChildNum - 1]; - else - node->u.internal.RightChild = NULL; - - // left child - if (node->u.internal.LeftChildNum > 0 && - room->TreeNodesCount > node->u.internal.LeftChildNum - 1) - node->u.internal.LeftChild = &room->TreeNodes[node->u.internal.LeftChildNum - 1]; - else - node->u.internal.LeftChild = NULL; - } - - // leafs - else if (node->Type == BspLeafType) - { - // sector this leaf belongs to - if (node->u.leaf.SectorNum > 0 && - room->SectorsCount > node->u.leaf.SectorNum - 1) - node->u.leaf.Sector = &room->Sectors[node->u.leaf.SectorNum - 1]; - else - node->u.leaf.Sector = NULL; - } + BspNode* node = &room->TreeNodes[i]; + + // internal nodes + if (node->Type == BspInternalType) + { + // first wall + if (node->u.internal.FirstWallNum > 0 && + room->WallsCount > node->u.internal.FirstWallNum - 1) + node->u.internal.FirstWall = &room->Walls[node->u.internal.FirstWallNum - 1]; + else + node->u.internal.FirstWall = NULL; + + // right child + if (node->u.internal.RightChildNum > 0 && + room->TreeNodesCount > node->u.internal.RightChildNum - 1) + node->u.internal.RightChild = &room->TreeNodes[node->u.internal.RightChildNum - 1]; + else + node->u.internal.RightChild = NULL; + + // left child + if (node->u.internal.LeftChildNum > 0 && + room->TreeNodesCount > node->u.internal.LeftChildNum - 1) + node->u.internal.LeftChild = &room->TreeNodes[node->u.internal.LeftChildNum - 1]; + else + node->u.internal.LeftChild = NULL; + } + + // leafs + else if (node->Type == BspLeafType) + { + // sector this leaf belongs to + if (node->u.leaf.SectorNum > 0 && + room->SectorsCount > node->u.leaf.SectorNum - 1) + node->u.leaf.Sector = &room->Sectors[node->u.leaf.SectorNum - 1]; + else + node->u.leaf.Sector = NULL; + } } /*************************************************************************/ @@ -1900,29 +2168,37 @@ bool BSPLoadRoom(char *fname, room_type *room) for (int i = 0; i < room->TreeNodesCount; i++) { - BspNode* node = &room->TreeNodes[i]; + BspNode* node = &room->TreeNodes[i]; - if (node->Type != BspLeafType) - continue; + if (node->Type != BspLeafType) + continue; - for (int j = 0; j < node->u.leaf.PointsCount; j++) - { - if (!node->u.leaf.Sector) - continue; + for (int j = 0; j < node->u.leaf.PointsCount; j++) + { + if (!node->u.leaf.Sector) + continue; - V2 p = { node->u.leaf.PointsFloor[j].X, node->u.leaf.PointsFloor[j].Y }; + V2 p = { node->u.leaf.PointsFloor[j].X, node->u.leaf.PointsFloor[j].Y }; - node->u.leaf.PointsFloor[j].Z = - SECTORHEIGHTFLOOR(node->u.leaf.Sector, &p); + node->u.leaf.PointsFloor[j].Z = + SECTORHEIGHTFLOOR(node->u.leaf.Sector, &p); - node->u.leaf.PointsCeiling[j].Z = - SECTORHEIGHTCEILING(node->u.leaf.Sector, &p); - } + node->u.leaf.PointsCeiling[j].Z = + SECTORHEIGHTCEILING(node->u.leaf.Sector, &p); + } } - /****************************************************************************/ /****************************************************************************/ + room->NextPathIdx = 0; + room->NextNoPathIdx = 0; + + for (int i = 0; i < PATHCACHESIZE; i++) + room->Paths[i] = new astar_path(); + + for (int i = 0; i < NOPATHCACHESIZE; i++) + room->NoPaths[i] = new astar_nopath(); + // no initial blockers room->Blocker = NULL; @@ -1932,23 +2208,35 @@ bool BSPLoadRoom(char *fname, room_type *room) /*********************************************************************************************/ /* BSPRoomFreeServer: Free the parts of a room structure used by the server. */ /*********************************************************************************************/ -void BSPFreeRoom(room_type *room) +void BSPFreeRoom(room_type *room, astar* Astar) { int i; /****************************************************************************/ /* CLIENT PARTS */ /****************************************************************************/ - + // free bsp nodes 'submem' for (i = 0; i < room->TreeNodesCount; i++) { if (room->TreeNodes[i].Type == BspLeafType) { - FreeMemory(MALLOC_ID_ROOM, room->TreeNodes[i].u.leaf.PointsFloor, - room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); - FreeMemory(MALLOC_ID_ROOM, room->TreeNodes[i].u.leaf.PointsCeiling, - room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); + if (Astar) + { + AStarFree(Astar, room->TreeNodes[i].u.leaf.PointsFloor, + room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); + + AStarFree(Astar, room->TreeNodes[i].u.leaf.PointsCeiling, + room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); + } + else + { + FreeMemory(MALLOC_ID_ROOM, room->TreeNodes[i].u.leaf.PointsFloor, + room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); + + FreeMemory(MALLOC_ID_ROOM, room->TreeNodes[i].u.leaf.PointsCeiling, + room->TreeNodes[i].u.leaf.PointsCount * sizeof(V3)); + } } } @@ -1956,15 +2244,34 @@ void BSPFreeRoom(room_type *room) for (i = 0; i < room->SectorsCount; i++) { if ((room->Sectors[i].Flags & SF_SLOPED_FLOOR) == SF_SLOPED_FLOOR) - FreeMemory(MALLOC_ID_ROOM, room->Sectors[i].SlopeInfoFloor, sizeof(SlopeInfo)); - if ((room->Sectors[i].Flags & SF_SLOPED_CEILING) == SF_SLOPED_CEILING) - FreeMemory(MALLOC_ID_ROOM, room->Sectors[i].SlopeInfoCeiling, sizeof(SlopeInfo)); + { + (Astar) ? + AStarFree(Astar, room->Sectors[i].SlopeInfoFloor, sizeof(SlopeInfo)) : + FreeMemory(MALLOC_ID_ROOM, room->Sectors[i].SlopeInfoFloor, sizeof(SlopeInfo)); + } + + if ((room->Sectors[i].Flags & SF_SLOPED_CEILING) == SF_SLOPED_CEILING) + { + (Astar) ? + AStarFree(Astar, room->Sectors[i].SlopeInfoCeiling, sizeof(SlopeInfo)) : + FreeMemory(MALLOC_ID_ROOM, room->Sectors[i].SlopeInfoCeiling, sizeof(SlopeInfo)); + } } - FreeMemory(MALLOC_ID_ROOM, room->TreeNodes, room->TreeNodesCount * sizeof(BspNode)); - FreeMemory(MALLOC_ID_ROOM, room->Walls, room->WallsCount * sizeof(Wall)); - FreeMemory(MALLOC_ID_ROOM, room->Sides, room->SidesCount * sizeof(Side)); - FreeMemory(MALLOC_ID_ROOM, room->Sectors, room->SectorsCount * sizeof(Sector)); + if (Astar) + { + AStarFree(Astar, room->TreeNodes, room->TreeNodesCount * sizeof(BspNode)); + AStarFree(Astar, room->Walls, room->WallsCount * sizeof(Wall)); + AStarFree(Astar, room->Sides, room->SidesCount * sizeof(Side)); + AStarFree(Astar, room->Sectors, room->SectorsCount * sizeof(Sector)); + } + else + { + FreeMemory(MALLOC_ID_ROOM, room->TreeNodes, room->TreeNodesCount * sizeof(BspNode)); + FreeMemory(MALLOC_ID_ROOM, room->Walls, room->WallsCount * sizeof(Wall)); + FreeMemory(MALLOC_ID_ROOM, room->Sides, room->SidesCount * sizeof(Side)); + FreeMemory(MALLOC_ID_ROOM, room->Sectors, room->SectorsCount * sizeof(Sector)); + } room->TreeNodesCount = 0; room->WallsCount = 0; @@ -1972,6 +2279,13 @@ void BSPFreeRoom(room_type *room) room->SectorsCount = 0; BSPBlockerClear(room); + BSPClearPathCache(room); + + for (int i = 0; i < PATHCACHESIZE; i++) + delete room->Paths[i]; + + for (int i = 0; i < NOPATHCACHESIZE; i++) + delete room->NoPaths[i]; /****************************************************************************/ /* SERVER PARTS */ diff --git a/blakserv/roofile.h b/blakserv/roofile.h index 6847d55b32..bb0f08c23e 100644 --- a/blakserv/roofile.h +++ b/blakserv/roofile.h @@ -13,8 +13,6 @@ #ifndef _ROOFILE_H #define _ROOFILE_H -#include "geometry.h" - #pragma region Macros /**************************************************************************************************************/ /* MACROS */ @@ -53,12 +51,11 @@ // value exactly expressable in KOD fineness units #define ROUNDROOTOKODFINENESS(a) FINENESSKODTOROO(roundf(FINENESSROOTOKOD(a))) -// from blakston.khd, used in BSPGetNextStepTowards across calls -#define ESTATE_AVOIDING 0x00004000 -#define ESTATE_CLOCKWISE 0x00008000 - // from blakston.khd, used for monster that can move outside BSP tree -#define MSTATE_MOVE_OUTSIDE_BSP 0x00100000 +#define MF_MOVE_OUTSIDE_BSP 0x00100000 +#define MF_LONG_STEP 0x00200000 +#define MF_AVOIDING 0x00400000 +#define MF_CLOCKWISE 0x00800000 // query flags for BSPGetLocationInfo #define LIQ_GET_SECTORINFO 0x00000001 @@ -84,6 +81,17 @@ /**************************************************************************************************************/ /* STRUCTS */ /**************************************************************************************************************/ + +// must match blakston.khd +typedef enum MoveCallbackType +{ + MC_UNREACHABLE = 0, + MC_STRAIGHTLINE = 1, + MC_PATHFROMCACHE = 2, + MC_NEWPATH = 3, + MC_HEURISTIC = 4 +} MoveCallbackType; + typedef struct BoundingBox2D { V2 Min; @@ -211,7 +219,13 @@ typedef struct room_type Side* Sides; unsigned short SidesCount; Sector* Sectors; - unsigned short SectorsCount; + unsigned short SectorsCount; + + astar_path* Paths[PATHCACHESIZE]; + unsigned int NextPathIdx; + + astar_nopath* NoPaths[NOPATHCACHESIZE]; + unsigned int NextNoPathIdx; } room_type; #pragma endregion @@ -220,19 +234,24 @@ typedef struct room_type /* METHODS */ /**************************************************************************************************************/ bool BSPGetHeight(room_type* Room, V2* P, float* HeightF, float* HeightFWD, float* HeightC, BspLeaf** Leaf); -bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOutsideBSP, Wall** BlockWall); +bool BSPCanMoveInRoom(room_type* Room, V2* S, V2* E, int ObjectID, bool moveOutsideBSP, Wall** BlockWall, bool SkipBlockers); bool BSPLineOfSight(room_type* Room, V3* S, V3* E); void BSPChangeTexture(room_type* Room, unsigned int ServerID, unsigned short NewTexture, unsigned int Flags); void BSPMoveSector(room_type* Room, unsigned int ServerID, bool Floor, float Height, float Speed); bool BSPGetLocationInfo(room_type* Room, V2* P, unsigned int QueryFlags, unsigned int* ReturnFlags, float* HeightF, float* HeightFWD, float* HeightC, BspLeaf** Leaf); bool BSPGetRandomPoint(room_type* Room, int MaxAttempts, V2* P); -bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID); +void BSPGetStepTowards(room_type* Room, V2* S, V2* E, unsigned int Flags, int ObjectID); +bool BSPGetStepFromHeuristic(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID); +bool BSPGetStepFromPath(room_type* Room, astar_path* Path, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID); +bool BSPGetStepFromPathCache(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID); bool BSPBlockerAdd(room_type* Room, int ObjectID, V2* P); bool BSPBlockerMove(room_type* Room, int ObjectID, V2* P); bool BSPBlockerRemove(room_type* Room, int ObjectID); void BSPBlockerClear(room_type* Room); -bool BSPLoadRoom(char *fname, room_type *room); -void BSPFreeRoom(room_type *room); +bool BSPLoadRoom(char* fname, room_type* room, astar* Astar); +void BSPFreeRoom(room_type* room, astar* Astar); +void BSPInvokeMoveCallback(int ObjectID, int Type, unsigned int State, V2* P); +void BSPClearPath(astar_path* Path); #pragma endregion #endif diff --git a/blakserv/roomdata.c b/blakserv/roomdata.c index 611b79d575..160e9f7f02 100644 --- a/blakserv/roomdata.c +++ b/blakserv/roomdata.c @@ -72,8 +72,11 @@ void ResetRooms() room = rooms[i % INIT_ROOMTABLE_SIZE]; while (room) { + // send command to unload from astar + AStarEnqueueCommand(new astar_command_unloadroom(room->data.roomdata_id)); + temp = room->next; - BSPFreeRoom(&room->data); + BSPFreeRoom(&room->data, NULL); FreeMemory(MALLOC_ID_ROOM, room, sizeof(room_node)); room = temp; } @@ -128,7 +131,7 @@ int LoadRoom(int resource_id) sprintf(s, "%s%s", ConfigStr(PATH_ROOMS), r->resource_val[0]); // try load it - if (!BSPLoadRoom(s, &newnode->data)) + if (!BSPLoadRoom(s, &newnode->data, NULL)) { FreeMemory(MALLOC_ID_ROOM, newnode, sizeof(room_node)); bprintf("LoadRoomData couldn't open %s!!!\n",r->resource_val[0]); @@ -150,6 +153,10 @@ int LoadRoom(int resource_id) room_rscs[resource_id % INIT_ROOMTABLE_SIZE] = rrsc; ret_val.v.data = newnode->data.roomdata_id; + + // also load room in astar workers, using roomdata_id for mapping + AStarEnqueueCommand(new astar_command_loadroom(s, newnode->data.roomdata_id)); + return ret_val.int_val; } @@ -192,7 +199,10 @@ void UnloadRoom(room_node *r) temp = room->next; if (room->data.roomdata_id == r->data.roomdata_id) { - BSPFreeRoom(&room->data); + // send command to unload from astar workers + AStarEnqueueCommand(new astar_command_unloadroom(room->data.roomdata_id)); + + BSPFreeRoom(&room->data, NULL); room = temp; rooms[r->data.roomdata_id % INIT_ROOMTABLE_SIZE] = temp; return; diff --git a/blakserv/timer.c b/blakserv/timer.c index 72be62bfc7..103f035a40 100644 --- a/blakserv/timer.c +++ b/blakserv/timer.c @@ -348,7 +348,7 @@ void ServiceTimers(void) if (ms > 500) ms = 500; } - + if (MsgWaitForMultipleObjects(0,NULL,0,(DWORD)ms,QS_ALLINPUT) == WAIT_OBJECT_0) { while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) @@ -389,6 +389,11 @@ void ServiceTimers(void) LoadFromKod(msg.lParam); LeaveServerLock(); break; + case WM_BLAK_MAIN_PATH_READY: + EnterServerLock(); + AStarDeliverNextResponse(); + LeaveServerLock(); + break; default : dprintf("ServiceTimers got unknown message %i\n",msg.message); diff --git a/kod/include/blakston.khd b/kod/include/blakston.khd index 38a6f84cf5..819bb99513 100644 --- a/kod/include/blakston.khd +++ b/kod/include/blakston.khd @@ -158,6 +158,13 @@ LIR_SECTOR_HASCTEX = 0x000040 LIR_BLOCKED_OBJECT = 0x000100 + % Types used in MoveCallback (must match roofile.h) + MC_UNREACHABLE = 0 + MC_STRAIGHTLINE = 1 + MC_PATHFROMCACHE = 2 + MC_NEWPATH = 3 + MC_HEURISTIC = 4 + %%% Projectile and Light flags % Flags for spells/projectiles @@ -1607,6 +1614,7 @@ STATE_MOVE = 0x00010 STATE_ZERO_MASK = 0xFF000 + ESTATE_LONG_STEP = 0x02000 ESTATE_AVOIDING = 0x04000 ESTATE_CLOCKWISE = 0x08000 @@ -1618,11 +1626,13 @@ VSTATE_INVALID_ATTACK = 0x20000 VSTATE_VALIDITY_MASK = 0x0FFFF - % Flag passed to BSP C calls to signify the monster - % can move outside the BSP tree. Mobs that can do this - % have the AI_MOVE_WALKTHROUGH_WALLS AI flag. - MSTATE_MOVE_OUTSIDE_BSP = 0x100000 - + % Flags passed to BSP GetStepTowards calls + % These are based on others + MF_MOVE_OUTSIDE_BSP = 0x100000 + MF_LONG_STEP = 0x200000 + MF_AVOIDING = 0x400000 + MF_CLOCKWISE = 0x800000 + % brain identification numbers BRAIN_ORIGINAL = 1 BRAIN_REVENANT = 2 @@ -1676,7 +1686,9 @@ AI_FIGHT_SWITCHALOT = 0x0800000 % monster may strike through walls (revenants) AI_FIGHT_THROUGH_WALLS = 0x1000000 - + % sometimes monsters need to wait a bit for next move available + AI_MOVE_WAITING_FOR_STEP = 0x2000000 + % Monster movement speeds SPEED_NONE = 0 SPEED_VERY_SLOW = 4 diff --git a/kod/object/active/holder/nomoveon/battler/monster.kod b/kod/object/active/holder/nomoveon/battler/monster.kod index 21a50f8274..df72288c5a 100644 --- a/kod/object/active/holder/nomoveon/battler/monster.kod +++ b/kod/object/active/holder/nomoveon/battler/monster.kod @@ -494,6 +494,11 @@ properties: % 20 seconds default for players, 60 for monsters piHurtMeTime = 60000 + pbNextMoveFaceTarget = FALSE + pbNextMoveFaceAway = FALSE + pbNextMoveToMaster = FALSE + poMoveStartedRoom = $ + messages: Constructor(template=FALSE, color=0, piSurvivalLevel=0, iBrain = 0) @@ -1621,7 +1626,7 @@ messages: { lBehavior = Cons(AI_LOOPING_PATROL,lBehavior); } - + piBehavior = viDefault_behavior; foreach i in lBehavior @@ -2513,17 +2518,19 @@ messages: if oTarget = $ { Debug("Bad info passed to MoveTowards!"); - return FALSE; - } - - return Send(self, @MoveTowardsCoords, - #iRow=Send(oTarget,@GetRow), - #iCol=Send(oTarget,@GetCol), - #iFineRow=Send(oTarget,@GetFineRow), - #iFineCol=Send(oTarget,@GetFineCol), - #face_target=face_target, - #face_away=face_away, - #to_master=to_master); + return; + } + + Send(self, @MoveTowardsCoords, + #iRow=Send(oTarget,@GetRow), + #iCol=Send(oTarget,@GetCol), + #iFineRow=Send(oTarget,@GetFineRow), + #iFineCol=Send(oTarget,@GetFineCol), + #face_target=face_target, + #face_away=face_away, + #to_master=to_master); + + return; } MoveAway(oTarget = $, face_target=FALSE, face_away=FALSE) @@ -2533,12 +2540,12 @@ messages: if oTarget = $ { Debug("Bad info passed to MoveAway!"); - return FALSE; + return; } if poOwner <> Send(oTarget,@GetOwner) { - return FALSE; + return; } % calculate square delta in fineness @@ -2549,13 +2556,15 @@ messages: dcol = dcol - ((Send(oTarget,@GetCol) * FINENESS) + Send(oTarget,@GetFineCol)); % add vector from oTarget to self on our position and try move there - return Send(self, @MoveTowardsCoords, - #iRow=(piRow + (drow / FINENESS)), - #iCol=(piCol + (dcol / FINENESS)), - #iFineRow=(piFine_row + (drow MOD FINENESS)), - #iFineCol=(piFine_col + (dcol MOD FINENESS)), - #face_target=face_target, - #face_away=face_away); + Send(self, @MoveTowardsCoords, + #iRow=(piRow + (drow / FINENESS)), + #iCol=(piCol + (dcol / FINENESS)), + #iFineRow=(piFine_row + (drow MOD FINENESS)), + #iFineCol=(piFine_col + (dcol MOD FINENESS)), + #face_target=face_target, + #face_away=face_away); + + return; } ReqMonsterMove(new_row = $,new_col = $,new_finerow = FINENESS_HALF, @@ -2629,44 +2638,115 @@ messages: MoveTowardsCoords(iRow=$, iCol=$, iFineRow=FINENESS_HALF, iFineCol=FINENESS_HALF, face_target=FALSE, face_away=FALSE, to_master=FALSE) { - local iState, iMoveFlags; + local iMoveFlags; if (iRow = $ or iCol = $) { return; } - % Mobs that can move outside the BSP tree set this field which is then - % combined with the state flags and sent to GetStepTowardsBSP. These - % moves aren't checked against room geometry. - iMoveFlags = 0; + % build flags for query here from different flag fields + iMoveFlags = 0; if (piBehavior & AI_MOVE_WALKTHROUGH_WALLS) { - iMoveFlags = iMoveFlags | MSTATE_MOVE_OUTSIDE_BSP; + iMoveFlags = iMoveFlags | MF_MOVE_OUTSIDE_BSP; } - + if (piState & ESTATE_AVOIDING) + { + iMoveFlags = iMoveFlags | MF_AVOIDING; + } + if (piState & ESTATE_CLOCKWISE) + { + iMoveFlags = iMoveFlags | MF_CLOCKWISE; + } + + % save some parameters of this move for the callback + pbNextMoveFaceTarget = face_target; + pbNextMoveFaceAway = face_away; + pbNextMoveToMaster = to_master; + poMoveStartedRoom = poOwner; + + % set flag on behaviour, unset in callback + piBehavior = piBehavior | AI_MOVE_WAITING_FOR_STEP; + % query where to step next % note: these need some persistent info across % calls which are stored in piState (flags from old KOD code) - iState = GetStepTowardsBSP( + GetStepTowardsBSP( Send(poOwner, @GetRoomData), piRow, piCol, piFine_row, piFine_col, - *iRow, *iCol, *iFineRow, *iFineCol, - piState | iMoveFlags, self); + iRow, iCol, iFineRow, iFineCol, + iMoveFlags, self); + + % may have no an answer here ready yet, or already processed, see MoveCallback + return; + } + + MoveCallback(iType=0, iFlags=0, iRow=0, iCol=0, iFineRow=0, iFineCol=0) + "Called from C when the next-step is available." + "This may happen during GetStepTowardsBSP() and therefore BEFORE" + "MoveTowardsCoords() ends, or at any later time if path needs to be calculated." + { + %debug("movecallback",iType,iFlags,iRow,iCol,iFineRow,iFineCol); + + % not waiting for a response anymore, e.g. serversave occured or others like + % we lost aggro on that target while path was calculated.. + if NOT (piBehavior & AI_MOVE_WAITING_FOR_STEP) + { + debug("Monster ", self, " received MoveCallback without waiting for it."); + return; + } + + % unset waiting flag on behaviour + piBehavior = piBehavior & ~AI_MOVE_WAITING_FOR_STEP; - if iState <> $ + % move return is invalid by now, because we changed rooms + if (poMoveStartedRoom <> poOwner OR poOwner = $) { - piState = iState; + % unset some flags + piState = piState & ~ESTATE_LONG_STEP; + piState = piState & ~ESTATE_AVOIDING; + piState = piState & ~ESTATE_CLOCKWISE; + return; + } - % debug("step",iRow,iCol,iFineRow,iFineCol); + % apply some returned move-flags on their original flag fields + if (iFlags & MF_LONG_STEP) + { + piState = piState | ESTATE_LONG_STEP; + } + else + { + piState = piState & ~ESTATE_LONG_STEP; + } + if (iFlags & MF_AVOIDING) + { + piState = piState | ESTATE_AVOIDING; + } + else + { + piState = piState & ~ESTATE_AVOIDING; + } + if (iFlags & MF_CLOCKWISE) + { + piState = piState | ESTATE_CLOCKWISE; + } + else + { + piState = piState & ~ESTATE_CLOCKWISE; + } + % all reachable types with valid position are bigger MC_UNREACHABLE + % right now we don't care what specifc kind + if iType > MC_UNREACHABLE + { if (NOT (piBehavior & AI_MOVE_WALKTHROUGH_WALLS)) OR Send(self,@ReqMonsterMove,#new_row=iRow,#new_col=iCol, #new_finerow=iFineRow,#new_finecol=iFineCol) { Send(self,@MonsterOrient,#new_row=iRow,#new_col=iCol, #new_finerow=iFineRow,#new_finecol=iFineCol, - #face_target=face_target,#face_away=face_away); + #face_target=pbNextMoveFaceTarget,#face_away=pbNextMoveFaceAway); Send(poOwner,@SomethingMoved,#what=self,#speed=viSpeed, #new_row=iRow,#new_col=iCol, @@ -2676,15 +2756,28 @@ messages: % Turn to face target Send(self,@MonsterOrient,#new_row=iRow,#new_col=iCol, #new_finerow=iFineRow,#new_finecol=iFineCol, - #face_target=face_target,#face_away=face_away); - - return TRUE; + #face_target=pbNextMoveFaceTarget,#face_away=pbNextMoveFaceAway); } } + else + { + % fully blocked: this is unlikely to happen + % because if no path -> move heuristc was used + } - return FALSE; + return; } + + GarbageCollecting() + "Unsets flags of pending moves across GC, because there" + "won't be any response." + { + % unset waiting flag on behaviour + piBehavior = piBehavior & ~AI_MOVE_WAITING_FOR_STEP; + return; + } + GotoCoords(iRow=$,iCol=$,iFineRow=FINENESS_HALF,iFineCol=FINENESS_HALF) "Move to a specific row and column. warning! fineness really" "isn't accurate as the monster will stop when it reaches the destination" @@ -5339,6 +5432,13 @@ messages: % see formula above iTime = 10000 / (viSpeed * 4); + % diagonal move: scale-up the time according to the increased distance + if piState & ESTATE_LONG_STEP + { + % sqrt(2) = 1.41421356 + iTime = (14142 * iTime) / 10000; + } + % finally we reduce the timer a bit (right now it's exact at ms) % why? the client should get a new position before the last destination % is reached. This prevents cases of stuttering if our message was a diff --git a/kod/object/passive/brain.kod b/kod/object/passive/brain.kod index b0fc962313..53620293a0 100644 --- a/kod/object/passive/brain.kod +++ b/kod/object/passive/brain.kod @@ -564,6 +564,12 @@ messages: return; } + % waiting on a path response + if (behavior & AI_MOVE_WAITING_FOR_STEP) + { + return; + } + if (behavior & AI_HAS_TARGET_LOCATION) { Send(mob,@DoGoTowardsLocation); diff --git a/kod/util/system.kod b/kod/util/system.kod index b0c02d4317..6a42b9501c 100644 --- a/kod/util/system.kod +++ b/kod/util/system.kod @@ -1001,6 +1001,8 @@ messages: Send(poStatistics,@GarbageCollecting); + Send(&Monster, @GarbageCollecting); + return; }