2013-02-16 16:14:39 -08:00

592 lines
19 KiB
C++

/*
Fear Pathing generation utility.
(c) 2005 Father Nitwit
Settings table:
CREATE TABLE fear_settings (
zone VARCHAR(16) NOT NULL PRIMARY KEY,
#general settings:
use_doors TINYINT NOT NULL DEFAULT 1,
min_fix_z FLOAT NOT NULL DEFAULT 20,
max_fear_distance FLOAT NOT NULL DEFAULT 250,
image_scale TINYINT NOT NULL DEFAULT 4,
#path related
check_initial_los TINYINT NOT NULL DEFAULT 0,
split_invalid_paths TINYINT NOT NULL DEFAULT 0,
link_path_endpoints TINYINT NOT NULL DEFAULT 1,
end_distance FLOAT NOT NULL DEFAULT 25,
split_long_min FLOAT NOT NULL DEFAULT 300,
split_long_step FLOAT NOT NULL DEFAULT 200,
#node combining settings:
same_dist FLOAT NOT NULL DEFAULT 2.5,
node_combine_dist FLOAT NOT NULL DEFAULT 30,
grid_combine_dist FLOAT NOT NULL DEFAULT 30,
close_all_los TINYINT NOT NULL DEFAULT 0,
#line-crossing reduction settings:
cross_count INT NOT NULL DEFAULT 5,
cross_min_length FLOAT NOT NULL DEFAULT 1,
cross_max_z_diff FLOAT NOT NULL DEFAULT 20,
cross_combine_dist FLOAT NOT NULL DEFAULT 120,
#linking:
second_link_dist FLOAT NOT NULL DEFAULT 100,
link_max_dist FLOAT NOT NULL DEFAULT 400,
link_count TINYINT NOT NULL DEFAULT 1
);
*/
#include "../common/types.h"
#include "../zone/map.h"
#include "../common/rdtsc.h"
#include "quadtree.h"
#include "apathing.h"
#include "boostcrap.h"
#include <stdio.h>
#include <mysql.h>
#include <stdlib.h>
#include <string.h>
#include <gd.h>
//parameters:
bool INCLUDE_DOORS = true;
float FEAR_MAXIMUM_DISTANCE = 250;
float ENDPOINT_CONNECT_MAX_DISTANCE = 25;
float MIN_FIX_Z = 20.0f;
float CLOSE_ENOUGH = 2.5;
bool COMBINE_CHECK_ALL_LOS = false;
float CLOSE_ENOUGH_COMBINE = 30; //30;
float SPAWN_MIN_SECOND_DIST = 100;
bool SPLIT_INVALID_PATHS = false;
bool LINK_PATH_ENDPOINTS = true;
float MAX_LINK_SPAWN_DIST = 400;
float FINAL_LINK_POINTS_DIST = 30;
bool SPAWN_LINK_TWICE = true;
bool SPAWN_LINK_THRICE = true;
float MERGE_MIN_SECOND_DIST = 30;
float SPLIT_LINE_LENGTH = 300;
float SPLIT_LINE_INTERVAL = 200;
float LONG_PATH_CHECK_LOS = 0; //0=disable
int CROSS_REDUCE_COUNT = 5;
float CROSS_MIN_LENGTH = 1;
float CROSS_MAX_Z_DIFF = 20;
float CLOSE_ENOUGH_CROSS = 120;
int IMAGE_SCALE = 4;
int main(int argc, char *argv[]) {
srand(2038833498);
/* const char *zone =
//"qeynos";
"qeynos2";
//"northkarana";
*/
if(argc != 2) {
printf("Usage: %s [zone_short_name]\n", argv[0]);
return(1);
}
const char *zone = argv[1];
char buf[256];
MYSQL m;
list<PathGraph *> db_paths;
list<PathNode *> db_spawns;
mysql_init(&m);
if(!mysql_real_connect(&m, DB_HOST, DB_LOGIN, DB_PASSWORD, DB_NAME, 0, NULL, 0)) {
printf("Unable to connect: %s.\n", mysql_error(&m));
return(1);
}
/*
Data loading phase
*/
//load up our map file
Map *map = Map::LoadMapfile(zone);
if(map == NULL) {
printf("Unable to load map file.");
return(1);
}
//try to load the EQ map file to make our pictures prettier
PathGraph eqmap;
if(load_eq_map(zone, &eqmap)) {
printf("Loaded EQ Client map: %d edges.\n", eqmap.edges.size());
} else {
printf("Unable to load EQ Client map, continuing without it.\n");
}
//load our crap from the DB...
if(!load_paths_from_db(&m, map, zone, db_paths, db_spawns))
return(1);
if(db_paths.size() == 0)
db_paths.push_back(new PathGraph());
if(!load_spawns_from_db(&m, zone, db_spawns))
return(1);
if(INCLUDE_DOORS) {
if(!load_doors_from_db(&m, zone, db_spawns))
return(1);
}
if(!load_hints_from_db(&m, zone, db_spawns))
return(1);
/*{
PathGraph *g;
PathNode *cur = NULL, *last = NULL;
g = new PathGraph();
g->nodes.push_back(cur = new PathNode(200, 200, 5));
last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(700, 200, 5)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(700, -300, 5)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(200, -300, 5)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(200, 200, 5)));
g->add_edge(last, cur); last = cur;
db_paths.push_back(g);
}*/
/*{
PathGraph *g;
PathNode *cur = NULL, *last = NULL;
g = new PathGraph();
g->nodes.push_back(cur = new PathNode(200, 200, 5));
last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(385, 35, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(450, -50, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(535, -115, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(700, -300, 5)));
g->add_edge(last, cur); last = cur;
db_paths.push_back(g);
g = new PathGraph();
g->nodes.push_back(cur = new PathNode(700, 200, 5));
last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(535, 35, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(450, -50, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(385, -115, 23)));
g->add_edge(last, cur); last = cur;
g->nodes.push_back(cur = new PathNode(GPoint(200, -300, 5)));
g->add_edge(last, cur); last = cur;
db_paths.push_back(g);
}*/
//try to load settings, dont care if it fails
if(load_settings_from_db(&m, zone))
printf("Loaded zone settings from the database.\n");
else
printf("Unable to load settings from database. Using defaults.\n");
printf("Load: got %d paths and %d spawn points from the database.\n", db_paths.size(), db_spawns.size());
printf("Load: had to split up %d invalid paths.\n", load_split_paths);
/*
The make-the-db-suck-less phase
*/
//try to lower waypoints way in the sky:
repair_high_waypoints(map, db_paths, db_spawns);
printf("Fix Z: %d missed map, %d were not broken, %d were fixed.\n", z_no_map_count, z_not_fixed_count, z_fixed_count);
printf("Fix Z: broken avg diff=%.3f, not broken avg diff=%.3f\n", z_fixed_diffs/z_fixed_count, z_not_fixed_diffs/z_not_fixed_count);
//run a waypoint reduction algorithm in 3space:
reduce_waypoints(db_paths);
printf("WP Reduce: removed %d redundant waypoints.\n", wp_reduce_count);
/*
Graph connection and merging phase
*/
//make trivial connections of nodes at about the same spot on diff grids
combine_trivial_grids(map, db_paths);
printf("Trivial Merge: %d grids merged.\n", trivial_merge_count);
//now do the 'closest with LOS' connection method
combine_closest_grids(map, db_paths);
printf("Closest Merge: %d grids linked, %d grids double-linked.\n", closest_merge_count, closest_merge2_count);
PathGraph *big = db_paths.front();
//now add in the spawn points, and link to closest with LOS
link_spawns(map, big, db_spawns, MAX_LINK_SPAWN_DIST, NULL);
printf("Link Spawns: %d linked once, %d linked twice, %d not linked, %d invalid.\n", link_spawn_count, link_spawn2_count, link_spawn_nocount, link_spawn_invalid);
//combining close points might be causing small LOS obstacles...
//so we want to run this before we combine them.
//this seems to do more harm than good right now
// check_edge_los(map, big);
// printf("Bad Edges: removed %d no-LOS edges.\n", removed_edges_los);
#ifdef LONG_PATH_CHECK_LOS
//check long paths LOS, we seem to have a problem with random long links
//disable this if we check all edges above...
check_long_edge_los(map, big);
printf("Bad Edges: removed %d long no-LOS edges.\n", removed_long_edges_los);
#endif
//clean up points close enough to eachother to be the same point.
combine_grid_points(map, big, CLOSE_ENOUGH_COMBINE);
printf("Point Combine: combined %d very close nodes (%d missed strict LOS).\n", combined_grid_points, combine_broke_los);
printf("Point Combine: so far, %d LOS cache hits, %d LOS cache misses.\n", los_cache_hits, los_cache_misses);
list<PathEdge *> all_edges = big->edges;
printf("Big Graph: %d original nodes, %d original edges.\n", big->nodes.size(), big->edges.size());
#ifdef DRAW_PRETREE_GRAPH
//draw out our graph before trimming
sprintf(buf, "paths-%s-pretree.png", zone);
draw_paths(map, big->edges, eqmap.edges, buf);
#endif
/*
Graph algorithm application (boost)
run it on each disjoint graph
*/
vector<PathGraph *> disjoints;
vector<int> start_nodes;
//find all the disjoint graphs
{
//build the boost graph
MyGraph boost_graph(big->nodes.size());
property_map<MyGraph, edge_weight_t>::type weightlist;
std::map<PathEdge *, EdgeDesc> edgemap;
build_boost_graph(boost_graph, weightlist, edgemap, big, false);
//find the grid which has most of the edges
sprintf(buf, "paths-%s-colors.png", zone);
find_disjoint_grids(map, boost_graph, big, buf, start_nodes, disjoints);
}
printf("\nSplit: There are %d valid disjoint graphs.\n", disjoints.size());
//for each disjoint graph....
int djnum = 0;
PathGraph *tmpg; int start_node;
vector<PathGraph *>::iterator cur,end;
vector<int>::iterator curs,ends;
cur = disjoints.begin(); curs = start_nodes.begin();
end = disjoints.end(); ends = start_nodes.end();
for(; cur != end; cur++,curs++) {
big = *cur;
start_node = *curs;
printf("Disjoint %d: has %d edges and %d nodes.\n", djnum, big->edges.size(), big->nodes.size());
//reset our stats...
combine_broke_los = 0;
combined_grid_points = 0;
removed_edges_los = 0;
removed_long_edges_los = 0;
broke_paths = 0;
cross_edge_count = 0;
cross_add_count = 0;
{
//build the boost graph
MyGraph boost_graph(big->nodes.size());
property_map<MyGraph, edge_weight_t>::type weightlist;
std::map<PathEdge *, EdgeDesc> edgemap;
build_boost_graph(boost_graph, weightlist, edgemap, big);
//calculate the MST
run_min_spanning_tree(boost_graph, weightlist, edgemap, big, start_node);
printf("Ran Min Spanning Tree: ended with %d edges\n", big->edges.size());
}
/*
Now we have our minimal spanning tree, try to refine it.
the goal of this crap is to fix newbie fields and open zones
*/
std::map<PathEdge*, vector<GPoint> > cross_list;
PathGraph *cross_big = new PathGraph();
PathGraph *cross_excess = new PathGraph();
//count the number of times each edge crosses another edge, and record
//the intersection points. Also seperate crossers from non-crossers
count_crossing_lines(big->edges, cross_big, cross_excess, cross_list);
printf("Cross Count: %d edges cross more than the specified number of other edges.\n", cross_edge_count);
if(cross_edge_count > 2) {
//Make waypoints at all points of intersection
cut_crossed_grids(cross_big, cross_list);
printf("Cross Cut: Created %d new nodes cutting intersections\n", cross_add_count);
//combine close points with a somewhat big radius...
combine_grid_points(map, cross_big, CLOSE_ENOUGH_CROSS);
printf("Cross Combine: combined %d nodes. (%d missed strict LOS)\n", combined_grid_points, combine_broke_los);
printf("Cross Combine: so far, %d LOS cache hits, %d LOS cache misses.\n", los_cache_hits, los_cache_misses);
//build our boost graph, so we can do reachability
MyGraph cross_graph(cross_big->nodes.size());
property_map<MyGraph, edge_weight_t>::type cross_weightlist;
std::map<PathEdge *, EdgeDesc> cross_edgemap;
build_boost_graph(cross_graph, cross_weightlist, cross_edgemap, cross_big, false);
//isolate each disjoint graph and try to reduce it, gathering
//all non-cross points and edges while we are at it.
sprintf(buf, "paths-%s-crosses.png", zone);
consolidate_cross_graphs(map, cross_big, cross_excess, cross_graph, buf);
//rebuild the big graph by merging cross_big and cross_excess
//might be as simple as append the two arrays and run a combine on it.
//leaks 'big'
*cur = big = cross_excess;
cross_excess->add_edges(cross_big->edges);
rebuild_node_list(big->edges, big->nodes);
//This is used to re-link the cross grids with the non-cross stuff
combine_grid_points(map, big, CLOSE_ENOUGH_COMBINE);
printf("Cross Merge: combined %d close nodes. (%d missed strict LOS)\n", combined_grid_points, combine_broke_los);
printf("Cross Merge: so far, %d LOS cache hits, %d LOS cache misses.\n", los_cache_hits, los_cache_misses);
//build yet another boost graph so we can run MST
MyGraph cross_graph_final(big->nodes.size());
property_map<MyGraph, edge_weight_t>::type cross_weightlist_final;
std::map<PathEdge *, EdgeDesc> cross_edgemap_final;
build_boost_graph(cross_graph_final, cross_weightlist_final, cross_edgemap_final, big);
//run our MST to reduce the new cross-reduced graph
run_min_spanning_tree(cross_graph_final, cross_weightlist_final, cross_edgemap_final, big, 0);
printf("Ran Min Spanning Tree 2: ended with %d edges\n", big->edges.size());
} //end if there were some cross edges
/*
Final refinement phase, the graph has been reduced to our final
form, this phase is for adding anything back in we might want
*/
//now that we reduced all our co-linear points, add a bunch back in for
//long paths, so we have more points over space. Idea is that this way
//we control how many colinear points there are, it isnt random
//this counteracts any attempts to use line-crossing as a criteria for reduction
//for some stupid reason, this just destroys the graph
// break_long_lines(db_paths);
// printf("WP Increase: created %d waypoints on long paths.\n", broke_paths);
/*
Things we might want to do:
- Try to create some cycles. Specifically large cycles.
- do this after reachability calculations
- use allready calculated reacahbility and distances, just adjust them as cycles added
- these will add more realism to the pathing
- might be implemented like this:
- run all pairs shortest path on the MST (is this a byproduct?)
- for each node N, for each other node K
- if path(N, K) is at least MIN_CYCLE_JOIN_PATH (to prevent making small cycles)
- and dist(N,K) is less than MAX_CYCLE_JOIN_DIST (do not want to invent long paths)
- and there is LOS from N to K
- connect N and K
- have to re-run all pairs again... that sucks
- another twist on the implementation would be to only look at paths
which were discarded by the MST. Or maybe run this first, then the other.
*/
/*
The final tree has been built, remove anything we dont need from
it, and gather some information.
*/
//build a boost graph out of our minimal spanning tree.
MyGraph boost_mst(big->nodes.size());
property_map<MyGraph, edge_weight_t>::type weightlist_mst;
std::map<PathEdge *, EdgeDesc> edgemap_mst;
build_boost_graph(boost_mst, weightlist_mst, edgemap_mst, big, true);
//determine the path lengths to all nodes from all others
//including the longest path reachable by each node.
//this also cleans up big by removing anything unreachable
// vector< vector<int> > AllPairs;
sprintf(buf, "paths-%s-mstcolors%d.png", zone, djnum++);
// calc_path_lengths(map, boost_mst, big, AllPairs, buf);
calc_path_lengths(map, boost_mst, big, edgemap_mst, buf);
printf("Calculated the longest paths from each node.\n");
printf("\n");
}
printf("Combining all disjoint graphs...\n");
//now combine all our disjoint graphs into one big one...
big = new PathGraph();
cur = disjoints.begin();
end = disjoints.end();
big->nodes.clear();
big->edges.clear();
djnum = 1;
for(; cur != end; cur++, djnum++) {
tmpg = *cur;
list<PathEdge *>::iterator cure,ende;
cure = tmpg->edges.begin();
ende = tmpg->edges.end();
for(; cure != ende; cure++) {
big->edges.push_back(*cure);
}
// sprintf(buf, "paths-%s-dj%d.png", zone, djnum-1);
// just_color_the_damned_thing(map, tmpg, buf);
}
rebuild_node_list(big->edges, big->nodes, NULL);
printf("Validating results...\n");
validate_edges(map, big);
//now we have our final node and edge set.
//build a graph of all final nodes to find pathing info
// MyGraph final(big->nodes.size());
// property_map<MyGraph, edge_weight_t>::type weightlist_final;
// std::map<PathEdge *, EdgeDesc> edgemap_final;
// build_boost_graph(final, weightlist_final, edgemap_final, big, true);
// sprintf(buf, "paths-%s-mstcolors%d-ppi.png", zone, djnum-1);
// just_color_the_damned_thing(map, big, buf);
vector< vector<PathEdge*> > path_finding;
find_path_info(map, big, path_finding);
printf("Calculated pathing information...\n");
//write out a nice image of our MST
sprintf(buf, "paths-%s-mstree.png", zone);
draw_paths2(map, all_edges, big->edges, eqmap.edges, db_spawns, buf);
//write out a map for inside the EQ client, not as pretty as the PNG
sprintf(buf, "eqmaps_out/%s_2.txt", zone);
write_eq_map(big->edges, buf);
/*
Build structures, and write out the path file.
*/
RDTSC_Timer t1, t2;
QTNode *root;
root = build_quadtree(map, big);
if(root == NULL) {
printf("Failed to build quadtree, quitting.\n");
return(1);
}
t2.start();
sprintf(buf, "%s.path", zone);
if(!write_path_file(root, big, buf, path_finding)) {
printf("Unable to write path file.\n");
return(1);
}
printf("Everything completed successfully. %s.path has been generated.\n", zone);
/*
** now we have a minimal connected graph such that any mob can get anywhere
by only a single path.
** now we have our final pathing grid. determine some useful info for each node.
look for dead ends/node preference:
- at each node, sum up the length of all edges reachable by using that link
- for each node as N
-- for each edge leaving N as E
---- reset marks on all edges
---- mark all edges leaving N
---- perform a depth first search of all reachable nodes
------ Only traverse unmarked edges. Mark each edge as it is traversed.
---- unmark all edges leaving N, except E
---- sum the length of all marked edges
---- store this reachable length as a weight for this edge of this node.
-- for each edge leaving N as E
---- determine highest edge weight in this node
-- for each edge leaving N as E
---- count number of edges within RANDOM_WALK % of the highest node.
-- sort the edge list for this node to have all random nodes first
-- store the random walkable counter for this node
** Finally we have all the calculation
things we need to store:
nodes
edge lists
quadtree containing nodes
- We do not really need to store edges which are not on the random walk
list since the pathing code will never consider using them.
Node {
x, y, z
edge list pointer
edge list length
random walkable counter
}
edge list entry {
ending node pointer
reachable edge weight? (not used by pathing, might have other purpose)
}
quadtree node {
minx, maxx, miny, maxy
union {
node pointers: q1, q2, q3, q4
node list offset and length
}
}
how to do fear pathing...
when a mob is feared, it uses the quadtree to find the node closest to it
that it can see to. The LOS requirement will make this more difficult, but feasible.
Once it has found its first node, the mob will set this as its waypoint and go there.
Once the mob reaches a node, it will look at the node's random counter (RC)
if RC is 1, take the first edge and use its terminal as next waypoint
if RC is >1, randomly choose an edge from 0 to RC-1, and walk that edge.
*/
mysql_close(&m);
}