diff --git a/blakserv/astar.c b/blakserv/astar.c new file mode 100644 index 0000000000..c6b887cfbc --- /dev/null +++ b/blakserv/astar.c @@ -0,0 +1,504 @@ +// 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" + +// refers to the i-th element on the heap of a room +#define HEAP(r, i) ((r)->Astar.NodesData[i].heapslot) + +bool AStarHeapCheck(room_type* Room, int i) +{ + int squares = Room->colshighres * Room->rowshighres; + + if (i >= squares) + return true; + + if (!HEAP(Room, i)) + return true; + + float our = HEAP(Room, i)->Data->combined; + int lchild = LCHILD(i); + int rchild = RCHILD(i); + + if (lchild < squares) + { + astar_node* node = HEAP(Room, lchild); + if (node) + { + if (node->Data->combined < our) + return false; + } + } + if (rchild < squares) + { + astar_node* node = HEAP(Room, rchild); + if (node) + { + if (node->Data->combined < our) + return false; + } + } + + bool retval = AStarHeapCheck(Room, lchild); + if (!retval) + return false; + + return AStarHeapCheck(Room, rchild); +} + +void AStarWriteHeapToFile(room_type* Room) +{ + int rows = Room->rowshighres; + int cols = Room->colshighres; + char* rowstring = (char *)AllocateMemory(MALLOC_ID_ROOM, 60000); + FILE *fp = fopen("heapdebug.txt", "a"); + if (fp) + { + int maxlayer = 9; + int treesize = Room->colshighres * Room->rowshighres; + 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 = Room->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); + } + FreeMemory(MALLOC_ID_ROOM, rowstring, 60000); +} + +__inline void AStarHeapSwap(room_type* Room, int idx1, int idx2) +{ + astar_node* node1 = HEAP(Room, idx1); + astar_node* node2 = HEAP(Room, idx2); + HEAP(Room, idx1) = node2; + HEAP(Room, idx2) = node1; + node1->Data->heapindex = idx2; + node2->Data->heapindex = idx1; +} + +__inline void AStarHeapMoveUp(room_type* Room, int Index) +{ + int i = Index; + while (i > 0 && HEAP(Room, i)->Data->combined < HEAP(Room, PARENT(i))->Data->combined) + { + AStarHeapSwap(Room, i, PARENT(i)); + i = PARENT(i); + } +} + +__inline void AStarHeapHeapify(room_type* Room, int Index) +{ + int i = Index; + do + { + int min = i; + if (HEAP(Room, LCHILD(i)) && HEAP(Room, LCHILD(i))->Data->combined < HEAP(Room, min)->Data->combined) + min = LCHILD(i); + if (HEAP(Room, RCHILD(i)) && HEAP(Room, RCHILD(i))->Data->combined < HEAP(Room, min)->Data->combined) + min = RCHILD(i); + if (min == i) + break; + AStarHeapSwap(Room, i, min); + i = min; + } + while (true); +} + +__inline void AStarHeapInsert(room_type* Room, astar_node* Node) +{ + // save index of this node + Node->Data->heapindex = Room->Astar.HeapSize; + + // add node at the end + HEAP(Room, Node->Data->heapindex) = Node; + + // increment + Room->Astar.HeapSize++; + + // push node up until in place + AStarHeapMoveUp(Room, Node->Data->heapindex); +} + +__inline void AStarHeapRemoveFirst(room_type* Room) +{ + int lastIdx = Room->Astar.HeapSize - 1; + + // no elements + if (lastIdx < 0) + return; + + // decrement size + Room->Astar.HeapSize--; + + // more than 1 + if (lastIdx > 0) + { + // put last at root + AStarHeapSwap(Room, 0, lastIdx); + + // zero out the previous root at swapped slot + HEAP(Room, lastIdx)->Data->heapindex = 0; + HEAP(Room, lastIdx) = NULL; + + // reorder tree + AStarHeapHeapify(Room, 0); + } + + // only one, clear head + else + { + HEAP(Room, 0)->Data->heapindex = 0; + HEAP(Room, 0) = NULL; + } +} + +__inline void AStarAddBlockers(room_type *Room) +{ + Blocker* b; + + b = Room->Blocker; + while (b) + { + // Don't add self. + if (b->ObjectID == Room->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 - Room->Astar.EndNode->Row) < DESTBLOCKIGNORE && + abs(col - Room->Astar.EndNode->Col) < DESTBLOCKIGNORE) + { + b = b->Next; + continue; + } + + // 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 >= Room->rowshighres || c >= Room->colshighres) + continue; + + astar_node* node = &Room->Astar.Grid[r][c]; + node->Data->isBlocked = true; + } + } + b = b->Next; + } +} + +__inline bool AStarProcessNode(room_type* Room) +{ + // shortcuts + astar_node* node = HEAP(Room, 0); + astar_node* endNode = Room->Astar.EndNode; + + Wall* blockWall; + + // openlist empty, unreachable + if (!node) + return false; + + // close enough, we're done, path found + if (abs(node->Col - endNode->Col) < CLOSEENOUGHDIST && + abs(node->Row - endNode->Row) < CLOSEENOUGHDIST) + { + Room->Astar.LastNode = node; + return false; + } + + // these 9 loop-iterations will go over all 8 adjacent squares and the node itself + // adjacent squares which are reachable will be added to the open-list with their costs + for (int rowoffset = -1; rowoffset < 2; rowoffset++) + { + for (int coloffset = -1; coloffset < 2; coloffset++) + { + // skip evaluating ourself + if (rowoffset == 0 && coloffset == 0) + continue; + + // indices of node to process + int r = node->Row + rowoffset; + int c = node->Col + coloffset; + + // outside + if (r < 0 || c < 0 || r >= Room->rowshighres || c >= Room->colshighres) + continue; + + // get candidate node for indices + astar_node* candidate = &Room->Astar.Grid[r][c]; + + // skip any already examined, outside or blocked node + if (candidate->Data->isInClosedList || !candidate->Leaf || candidate->Data->isBlocked) + continue; + + // can't move from node to this candidate + if (!BSPCanMoveInRoom(Room, &node->Location, &candidate->Location, Room->Astar.ObjectID, false, &blockWall, true)) + continue; + + // cost for diagonal is sqrt(2), otherwise 1 + float stepcost = ((rowoffset != 0) && (coloffset != 0)) ? COST_DIAG : COST; + + // heuristic (~ estimated distance from node to end) + // need to do this only once + if (candidate->Data->heuristic == 0.0f) + { + float dx = (float)abs(candidate->Col - endNode->Col); + float dy = (float)abs(candidate->Row - endNode->Row); + + // octile-distance + candidate->Data->heuristic = + COST * (dx + dy) + (COST_DIAG - 2.0f * COST) * fminf(dx, dy); + + // tie breaker and fixes h(nondiagonal) not lower exact cost + candidate->Data->heuristic *= 0.999f; + } + + // CASE 1) + // if this candidate has no parent yet (never visited before) + if (!candidate->Data->parent) + { + // cost to candidate is cost to Node + one step + candidate->Data->parent = node; + candidate->Data->cost = node->Data->cost + stepcost; + candidate->Data->combined = candidate->Data->cost + candidate->Data->heuristic; + + // add it sorted to the open list + AStarHeapInsert(Room, candidate); + } + + // 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 < candidate->Data->cost) + { + candidate->Data->parent = node; + candidate->Data->cost = newcost; + candidate->Data->combined = newcost + candidate->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(Room, candidate->Data->heapindex); + } + } + } + } + + /****************************************************************/ + + // mark this node processed + node->Data->isInClosedList = true; + + // remove it from the open list + AStarHeapRemoveFirst(Room); + + return true; +} + +void AStarWriteGridToFile(room_type* Room) +{ + int rows = Room->rowshighres; + int cols = Room->colshighres; + + char *rowstring = (char *)AllocateMemory(MALLOC_ID_ROOM, 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->Astar.Grid[row][col].Data->combined); + } + sprintf(rowstring, "%s \n", rowstring); + fputs(rowstring, fp); + } + fclose(fp); + } + + FreeMemory(MALLOC_ID_ROOM, rowstring, 50000); +} + +void AStarGenerateGrid(room_type* Room) +{ + // note: we allocate all memory for the astar_node_data of all squares together + // this is necessary so we can erase it with a single cheap ZeroMemory call.. + Room->Astar.NodesDataSize = Room->colshighres * Room->rowshighres * sizeof(astar_node_data); + + // allocate memory for all nodesdata (cleaned for each calculation) + Room->Astar.NodesData = (astar_node_data*)AllocateMemory( + MALLOC_ID_ASTAR, Room->Astar.NodesDataSize); + + // allocate memory for the persistent nodesinfo (typical 2d array by **) + Room->Astar.Grid = (astar_node**)AllocateMemory( + MALLOC_ID_ASTAR, Room->rowshighres * sizeof(astar_node*)); + + // setup rows + for (int i = 0; i < Room->rowshighres; i++) + Room->Astar.Grid[i] = (astar_node*)AllocateMemory( + MALLOC_ID_ASTAR, Room->colshighres * sizeof(astar_node)); + + // setup all squares + for (int i = 0; i < Room->rowshighres; i++) + { + for (int j = 0; j < Room->colshighres; j++) + { + astar_node* node = &Room->Astar.Grid[i][j]; + float f1, f2, f3; + 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; + + // mark if this square is inside or outside of room + BSPGetHeight(Room, &node->Location, &f1, &f2, &f3, &node->Leaf); + + // setup reference to data (costs etc.) in erasable mem area + node->Data = &Room->Astar.NodesData[i*Room->colshighres + j]; + } + } +} + +void AStarFreeGrid(room_type* Room) +{ + // free workdata mem + FreeMemory(MALLOC_ID_ASTAR, Room->Astar.NodesData, Room->Astar.NodesDataSize); + + // free each row mem + for (int i = 0; i < Room->rowshighres; i++) + FreeMemory(MALLOC_ID_ASTAR, Room->Astar.Grid[i], Room->colshighres * sizeof(astar_node)); + + // free rowsmem + FreeMemory(MALLOC_ID_ASTAR, Room->Astar.Grid, Room->rowshighres * sizeof(astar_node*)); +} + +bool AStarGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID) +{ + // convert coordinates from ROO floatingpoint to + // highres scale in integers + int startrow = (int)roundf(S->Y / 256.0f); + int startcol = (int)roundf(S->X / 256.0f); + int endrow = (int)roundf(E->Y / 256.0f); + int endcol = (int)roundf(E->X / 256.0f); + + // all 4 values must be within grid array bounds! + if (startrow < 0 || startrow >= Room->rowshighres || + startcol < 0 || startcol >= Room->colshighres || + endrow < 0 || endrow >= Room->rowshighres || + endcol < 0 || endcol >= Room->colshighres) + return false; + + /**********************************************************************/ + + // get start and endnode + astar_node* startnode = &Room->Astar.Grid[startrow][startcol]; + Room->Astar.EndNode = &Room->Astar.Grid[endrow][endcol]; + + // init the astar struct + Room->Astar.LastNode = NULL; + Room->Astar.ObjectID = ObjectID; + Room->Astar.HeapSize = 0; + + // prepare non-persistent astar grid data memory + ZeroMemory(Room->Astar.NodesData, Room->Astar.NodesDataSize); + + /**********************************************************************/ + + // mark nodes blocked by objects + AStarAddBlockers(Room); + + // insert startnode into heap-tree + AStarHeapInsert(Room, 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 (AStarProcessNode(Room)) { } + + //AStarWriteGridToFile(Room); + //AStarWriteHeapToFile(Room); + /**********************************************************************/ + + // now let's walk back our parent pointers starting from the LastNode + // this chain represents the path (if it was unreachable, it's null) + astar_node* parent = Room->Astar.LastNode; + while (parent && parent->Data->parent != startnode) + parent = parent->Data->parent; + + // unreachable + if (!parent) + return false; + + // for diagonal moves mark to be long step (required for timer elapse) + if (abs(parent->Col - startnode->Col) && + abs(parent->Row - startnode->Row)) + { + *Flags |= ESTATE_LONG_STEP; + } + else + *Flags &= ~ESTATE_LONG_STEP; + + // set step endpoint + *P = parent->Location; + + return true; +} diff --git a/blakserv/astar.h b/blakserv/astar.h new file mode 100644 index 0000000000..d454e22eb2 --- /dev/null +++ b/blakserv/astar.h @@ -0,0 +1,67 @@ +// 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 + +#define CLOSEENOUGHDIST 3 +#define DESTBLOCKIGNORE 3 +#define ASTARENABLED 1 + +#define LCHILD(x) (2 * x + 1) +#define RCHILD(x) (2 * x + 2) +#define PARENT(x) ((x-1) / 2) + +#define COST 1.0f +#define COST_DIAG ((float)M_SQRT2) + +typedef struct room_type room_type; +typedef struct BspLeaf BspLeaf; +typedef struct astar_node_data astar_node_data; +typedef struct astar_node astar_node; + +typedef struct astar_node_data +{ + float cost; + float heuristic; + float combined; + astar_node* parent; + astar_node* heapslot; + int heapindex; + bool isInClosedList; + bool isBlocked; +} astar_node_data; + +typedef struct astar_node +{ + int Row; + int Col; + V2 Location; + BspLeaf* Leaf; + astar_node_data* Data; +} astar_node; + +typedef struct astar +{ + astar_node_data* NodesData; + int NodesDataSize; + astar_node** Grid; + astar_node* EndNode; + astar_node* LastNode; + int ObjectID; + int HeapSize; +} astar; + +void AStarGenerateGrid(room_type* Room); +void AStarFreeGrid(room_type* Room); +bool AStarGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags, int ObjectID); + +#endif /*#ifndef _ASTAR_H */ \ No newline at end of file diff --git a/blakserv/blakserv.h b/blakserv/blakserv.h index 912234cc9f..651c9760ce 100644 --- a/blakserv/blakserv.h +++ b/blakserv/blakserv.h @@ -241,6 +241,8 @@ char * GetLastErrorStr(); #include "stringinthash.h" #include "intstringhash.h" +#include "geometry.h" + #include "blakres.h" #include "channel.h" #include "kodbase.h" @@ -257,6 +259,7 @@ char * GetLastErrorStr(); #include "system.h" #include "loadrsc.h" #include "loadgame.h" +#include "astar.h" #include "roofile.h" #include "roomdata.h" #include "files.h" diff --git a/blakserv/blakserv.vcxproj b/blakserv/blakserv.vcxproj index b17bf0ee56..d41514efc8 100644 --- a/blakserv/blakserv.vcxproj +++ b/blakserv/blakserv.vcxproj @@ -168,6 +168,7 @@ copy $(SolutionDir)bin\libcurl.dll $(SolutionDir)run\server + @@ -242,6 +243,7 @@ copy $(SolutionDir)bin\libcurl.dll $(SolutionDir)run\server + diff --git a/blakserv/blakserv.vcxproj.filters b/blakserv/blakserv.vcxproj.filters index 4da1eda561..7e16fa6f6d 100644 --- a/blakserv/blakserv.vcxproj.filters +++ b/blakserv/blakserv.vcxproj.filters @@ -230,6 +230,9 @@ Header Files + + Header Files + @@ -436,6 +439,9 @@ Source Files + + Source Files + diff --git a/blakserv/ccode.c b/blakserv/ccode.c index 6fa8ee4a6b..31e8855bd1 100644 --- a/blakserv/ccode.c +++ b/blakserv/ccode.c @@ -2675,7 +2675,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); diff --git a/blakserv/makefile b/blakserv/makefile index 005cd8f153..cc9436a9f3 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..3b1dfdd80c 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", diff --git a/blakserv/memory.h b/blakserv/memory.h index b2aedcfdf6..d9b63e0a43 100644 --- a/blakserv/memory.h +++ b/blakserv/memory.h @@ -21,7 +21,7 @@ enum MALLOC_ID_SYSTIMER, MALLOC_ID_NAMEID, MALLOC_ID_CLASS, MALLOC_ID_MESSAGE, MALLOC_ID_OBJECT, MALLOC_ID_LIST, MALLOC_ID_OBJECT_PROPERTIES, - MALLOC_ID_CONFIG, MALLOC_ID_ROOM, + MALLOC_ID_CONFIG, MALLOC_ID_ROOM, MALLOC_ID_ASTAR, MALLOC_ID_ADMIN_CONSTANTS, MALLOC_ID_BUFFER, MALLOC_ID_LOAD_GAME, MALLOC_ID_TABLE, MALLOC_ID_BLOCK, MALLOC_ID_SAVE_GAME, diff --git a/blakserv/roofile.c b/blakserv/roofile.c index a53796879e..b4a66ec201 100644 --- a/blakserv/roofile.c +++ b/blakserv/roofile.c @@ -634,7 +634,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; @@ -655,6 +655,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) @@ -940,7 +944,7 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags // but must not give these back in piState *Flags &= ~MSTATE_MOVE_OUTSIDE_BSP; - + V2 se, stepend; V2SUB(&se, E, S); @@ -965,252 +969,265 @@ bool BSPGetStepTowards(room_type* Room, V2* S, V2* E, V2* P, unsigned int* Flags V2SCALE(&se, scale); /****************************************************/ - // 1) try direct step towards destination first + // 1) test direct line towards destination first /****************************************************/ Wall* blockWall = NULL; - // 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, E, ObjectID, moveOutsideBSP, &blockWall, false)) { - *P = stepend; - *Flags &= ~ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return 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)) + { + *P = stepend; + *Flags &= ~ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } } - + /****************************************************/ // 2) can't do direct step /****************************************************/ - bool isAvoiding = ((*Flags & ESTATE_AVOIDING) == ESTATE_AVOIDING); - bool isLeft = ((*Flags & ESTATE_CLOCKWISE) == ESTATE_CLOCKWISE); - - // not yet in clockwise or cclockwise mode - if (!isAvoiding) + // try get next step from astar path if enabled + if (ASTARENABLED && AStarGetStepTowards(Room, S, E, P, Flags, ObjectID)) { - // if not blocked by a wall, roll a dice to decide - // how to get around the blocking obj. - if (!blockWall) - isLeft = (rand() % 2 == 1); + *Flags &= ~ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } + else + { + bool isAvoiding = ((*Flags & ESTATE_AVOIDING) == ESTATE_AVOIDING); + bool isLeft = ((*Flags & ESTATE_CLOCKWISE) == ESTATE_CLOCKWISE); - // blocked by wall, go first into 'slide-along' direction - // based on vector towards target - else + // not yet in clockwise or cclockwise mode + if (!isAvoiding) { - V2 p1p2; - V2SUB(&p1p2, &blockWall->P2, &blockWall->P1); - - // note: walls can be aligned in any direction like left->right, right->left, - // same with up->down and same also with the movement vector. - // The typical angle between vectors, acosf(..) is therefore insufficient to differ. - // What is done here is a convert into polar-coordinates (= angle in 0..2pi from x-axis) - // The difference (or sum) (-2pi..2pi) then provides up to 8 different cases (quadrants) which must be mapped - // to the left or right decision. - float f1 = atan2f(se.Y, se.X); - float f2 = atan2f(p1p2.Y, p1p2.X); - float df = f1 - f2; - - bool q1_pos = (df >= 0.0f && df <= (float)M_PI_2); - bool q2_pos = (df >= (float)M_PI_2 && df <= (float)M_PI); - bool q3_pos = (df >= (float)M_PI && df <= (float)(M_PI + M_PI_2)); - bool q4_pos = (df >= (float)(M_PI + M_PI_2) && df <= (float)M_PI*2.0f); - bool q1_neg = (df <= 0.0f && df >= (float)-M_PI_2); - 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 not blocked by a wall, roll a dice to decide + // how to get around the blocking obj. + if (!blockWall) + isLeft = (rand() % 2 == 1); - /*if (isLeft) - dprintf("trying left first r: %f", df); + // blocked by wall, go first into 'slide-along' direction + // based on vector towards target else - dprintf("trying right first r: %f", df);*/ + { + V2 p1p2; + V2SUB(&p1p2, &blockWall->P2, &blockWall->P1); + + // note: walls can be aligned in any direction like left->right, right->left, + // same with up->down and same also with the movement vector. + // The typical angle between vectors, acosf(..) is therefore insufficient to differ. + // What is done here is a convert into polar-coordinates (= angle in 0..2pi from x-axis) + // The difference (or sum) (-2pi..2pi) then provides up to 8 different cases (quadrants) which must be mapped + // to the left or right decision. + float f1 = atan2f(se.Y, se.X); + float f2 = atan2f(p1p2.Y, p1p2.X); + float df = f1 - f2; + + bool q1_pos = (df >= 0.0f && df <= (float)M_PI_2); + bool q2_pos = (df >= (float)M_PI_2 && df <= (float)M_PI); + bool q3_pos = (df >= (float)M_PI && df <= (float)(M_PI + M_PI_2)); + bool q4_pos = (df >= (float)(M_PI + M_PI_2) && df <= (float)M_PI*2.0f); + bool q1_neg = (df <= 0.0f && df >= (float)-M_PI_2); + 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);*/ + } } - } - // must run this possibly twice - // e.g. left after right failed or right after left failed - for (int i = 0; i < 2; i++) - { - if (isLeft) + // must run this possibly twice + // e.g. left after right failed or right after left failed + for (int i = 0; i < 2; i++) { - V2 v = se; - - // 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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; - return true; - } - - // try 45° 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 (isLeft) { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; - return true; - } + V2 v = se; + + // 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // try 67.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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; - return true; - } + // try 45° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // try 112.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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; - return true; - } + // try 67.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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // try 135° left - V2ROTATE(&v, (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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // failed to circumvent by going left, switch to right - isLeft = false; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - } - else - { - V2 v = se; - - // try 22.5° 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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return true; - } + // try 112.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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // try 45° 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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return true; - } + // try 135° left + V2ROTATE(&v, (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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags |= ESTATE_CLOCKWISE; + return true; + } - // try 67.5° 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)) - { - *P = stepend; + // failed to circumvent by going left, switch to right + isLeft = false; *Flags |= ESTATE_AVOIDING; *Flags &= ~ESTATE_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)) - { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return true; } - - // try 112.5° 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)) + else { - *P = stepend; - *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return true; - } + V2 v = se; + + // try 22.5° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } - // try 135° 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)) - { - *P = stepend; + // try 45° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } + + // try 67.5° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } + + // try 112.5° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } + + // try 135° 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, false)) + { + *P = stepend; + *Flags |= ESTATE_AVOIDING; + *Flags &= ~ESTATE_CLOCKWISE; + return true; + } + + // failed to circumvent by going right, switch to left + isLeft = true; *Flags |= ESTATE_AVOIDING; - *Flags &= ~ESTATE_CLOCKWISE; - return true; + *Flags |= ESTATE_CLOCKWISE; } - - // failed to circumvent by going right, switch to left - isLeft = true; - *Flags |= ESTATE_AVOIDING; - *Flags |= ESTATE_CLOCKWISE; } } @@ -1880,6 +1897,12 @@ bool BSPLoadRoom(char *fname, room_type *room) } } + /*************************************************************************/ + /* GENERATE ASTAR PERSISTENT DATA */ + /*************************************************************************/ + + AStarGenerateGrid(room); + /****************************************************************************/ /****************************************************************************/ @@ -1933,6 +1956,8 @@ void BSPFreeRoom(room_type *room) BSPBlockerClear(room); + AStarFreeGrid(room); + /****************************************************************************/ /* SERVER PARTS */ /****************************************************************************/ diff --git a/blakserv/roofile.h b/blakserv/roofile.h index 0d5f062340..071cf16ccc 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,6 +51,7 @@ #define ROUNDROOTOKODFINENESS(a) FINENESSKODTOROO(roundf(FINENESSROOTOKOD(a))) // from blakston.khd, used in BSPGetNextStepTowards across calls +#define ESTATE_LONG_STEP 0x00002000 #define ESTATE_AVOIDING 0x00004000 #define ESTATE_CLOCKWISE 0x00008000 @@ -210,7 +209,9 @@ typedef struct room_type Side* Sides; unsigned short SidesCount; Sector* Sectors; - unsigned short SectorsCount; + unsigned short SectorsCount; + + astar Astar; } room_type; #pragma endregion @@ -219,7 +220,7 @@ 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); diff --git a/kod/include/blakston.khd b/kod/include/blakston.khd index 38a6f84cf5..e0b37dec85 100644 --- a/kod/include/blakston.khd +++ b/kod/include/blakston.khd @@ -1607,6 +1607,7 @@ STATE_MOVE = 0x00010 STATE_ZERO_MASK = 0xFF000 + ESTATE_LONG_STEP = 0x02000 ESTATE_AVOIDING = 0x04000 ESTATE_CLOCKWISE = 0x08000 diff --git a/kod/object/active/holder/nomoveon/battler/monster.kod b/kod/object/active/holder/nomoveon/battler/monster.kod index b2fa4765c2..321b465684 100644 --- a/kod/object/active/holder/nomoveon/battler/monster.kod +++ b/kod/object/active/holder/nomoveon/battler/monster.kod @@ -5352,6 +5352,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/active/holder/room/monsroom/h5.kod b/kod/object/active/holder/room/monsroom/h5.kod index a36f980dd1..04e08b4c3c 100644 --- a/kod/object/active/holder/room/monsroom/h5.kod +++ b/kod/object/active/holder/room/monsroom/h5.kod @@ -63,6 +63,16 @@ messages: plGenerators = [ [19, 7], [22, 14], [27, 25], [25, 36], [38, 34], [46, 26], [36, 17], [35, 8] ]; + + plPatrolPaths = [ [[14,11],[13,4],[19,7]], + [[26,10],[22,7],[22,14]], + [[25,34],[20,27],[27,25]], + [[24,22],[25,36]], + [[44,33],[50,27],[38,34]], + [[48,21],[43,21],[46,26]], + [[26,16],[36,17]], + [[27,12],[38,6],[44,12],[35, 8]] ]; + propagate; } diff --git a/kod/object/active/holder/room/monsroom/h6.kod b/kod/object/active/holder/room/monsroom/h6.kod index ba2a02ccde..016fd5821f 100644 --- a/kod/object/active/holder/room/monsroom/h6.kod +++ b/kod/object/active/holder/room/monsroom/h6.kod @@ -73,6 +73,21 @@ messages: plGenerators = [ [8, 8], [9, 14], [5, 25], [10, 30], [18, 18], [25, 19], [20, 3], [25, 9], [42, 5], [45, 12], [41, 22], [35, 30], [47, 34] ]; + + plPatrolPaths = [ [$], + [[4,20],[9, 14]], + [$], + [$], + [[13,8],[25,18],[18, 18]], + [[21,32],[31,31],[25, 19]], + [$], + [$], + [[46,12],[37,12],[42, 5]], + [[45,18],[48,18],[45, 12]], + [[40,26],[37,26],[36,21],[40,19],[41, 22]], + [[54,33],[35, 30]], + [[17,11],[47, 34]] ]; + propagate; } diff --git a/kod/object/active/holder/room/monsroom/i6.kod b/kod/object/active/holder/room/monsroom/i6.kod index 8e0d34c624..b19f1b0986 100644 --- a/kod/object/active/holder/room/monsroom/i6.kod +++ b/kod/object/active/holder/room/monsroom/i6.kod @@ -65,6 +65,23 @@ messages: plGenerators = [ [8, 9], [11, 24], [19, 10], [26, 21], [33, 7], [36, 16], [35, 26], [41, 6], [43, 20], [46, 35], [38, 43], [29, 43], [20, 33], [9, 35], [16, 48]]; + + plPatrolPaths = [ [[14,7],[7,15],[8,9]], + [[6,27],[10,21],[11,24]], + [[10,4],[19,10]], + [[27,35],[26,21]], + [[37,12],[33,15],[28,12],[33,7]], + [[41,12],[39,25],[36,16]], + [[46,33],[35,26]], + [[17,5],[41,6]], + [[27,20],[43,20]], + [[34,45],[46,35]], + [[27,22],[38,43]], + [[10,34],[21,29],[29,43]], + [[34,25],[20,33]], + [[37,44],[9,35]], + [[7,44],[16,48]] ]; + propagate; } diff --git a/kod/object/active/holder/room/monsroom/i7.kod b/kod/object/active/holder/room/monsroom/i7.kod index d288fb03b6..72969cf428 100644 --- a/kod/object/active/holder/room/monsroom/i7.kod +++ b/kod/object/active/holder/room/monsroom/i7.kod @@ -66,6 +66,23 @@ messages: plGenerators = [ [9, 8], [4, 19], [18, 9], [18, 4], [20, 19], [7, 25], [5, 39], [26, 31], [35, 35], [40, 30], [32, 19], [40, 14], [41, 43], [44, 47], [33, 51] ]; + + plPatrolPaths = [ [[19,8],[8,2],[9, 8]], + [[4,15],[6,19],[3,22],[4,19]], + [[23,12],[18,9]], + [[19,6],[18,2],[18,4]], + [[12,24],[8,38],[20,19]], + [[11,27],[7,25]], + [[7,30],[5,39]], + [[20,34],[23,27],[29,31],[26,31]], + [[38,28],[35,35]], + [[41,4],[40,30]], + [$], + [[21,51],[40,14]], + [[43,49],[41,43]], + [[48,31],[44,47]], + [$] ]; + propagate; } diff --git a/kod/object/active/holder/room/monsroom/i8.kod b/kod/object/active/holder/room/monsroom/i8.kod index d5ae0738ec..48fd64eff4 100644 --- a/kod/object/active/holder/room/monsroom/i8.kod +++ b/kod/object/active/holder/room/monsroom/i8.kod @@ -65,6 +65,22 @@ messages: plGenerators = [ [3, 3], [7, 7], [19, 12], [22, 19], [20, 27], [16, 35], [26, 34], [30, 39], [30, 14], [36, 20], [38, 29], [48, 25], [54, 27], [50, 35] ]; + + plPatrolPaths = [ [[9,3],[7,8],[3,3]], + [[11,5],[16,12],[18,8],[7,7]], + [[19,16],[21,26],[17,12],[19,12]], + [[24,21],[27,27],[22,19]], + [[17,38],[20,27]], + [[17,28],[20,36],[16,35]], + [[30,31],[31,39],[26,34]], + [[25,33],[30,39]], + [[32,26],[30,14]], + [[36,30],[36,20]], + [[52,39],[38,29]], + [[35,20],[57,28],[48,25]], + [[52,20],[54,27]], + [[54,40],[50,35]] ]; + propagate; }