![]() |
Shadowrun: Awakened 29 September 2011 - Build 871
|
00001 00002 00003 00004 00005 00006 00007 00008 00009 00010 00011 #ifndef __WEIGHTED_GRAPH_H 00012 #define __WEIGHTED_GRAPH_H 00013 00014 #include "DS_OrderedList.h" 00015 #include "DS_Map.h" 00016 #include "DS_Heap.h" 00017 #include "DS_Queue.h" 00018 #include "DS_Tree.h" 00019 #include "RakAssert.h" 00020 #include "RakMemoryOverride.h" 00021 #ifdef _DEBUG 00022 #include <stdio.h> 00023 #endif 00024 00025 #ifdef _MSC_VER 00026 #pragma warning( push ) 00027 #endif 00028 00031 namespace DataStructures 00032 { 00033 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00034 class RAK_DLL_EXPORT WeightedGraph 00035 { 00036 public: 00037 static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison<node_type>(node_type(),node_type());} 00038 00039 WeightedGraph(); 00040 ~WeightedGraph(); 00041 WeightedGraph( const WeightedGraph& original_copy ); 00042 WeightedGraph& operator= ( const WeightedGraph& original_copy ); 00043 void AddNode(const node_type &node); 00044 void RemoveNode(const node_type &node); 00045 void AddConnection(const node_type &node1, const node_type &node2, weight_type weight); 00046 void RemoveConnection(const node_type &node1, const node_type &node2); 00047 bool HasConnection(const node_type &node1, const node_type &node2); 00048 void Print(void); 00049 void Clear(void); 00050 bool GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT); 00051 bool GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT ); 00052 unsigned GetNodeCount(void) const; 00053 unsigned GetConnectionCount(unsigned nodeIndex) const; 00054 void GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const; 00055 node_type GetNodeAtIndex(unsigned nodeIndex) const; 00056 00057 protected: 00058 void ClearDijkstra(void); 00059 void GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT); 00060 00061 DataStructures::Map<node_type, DataStructures::Map<node_type, weight_type> *> adjacencyLists; 00062 00063 // All these variables are for path finding with Dijkstra 00064 // 08/23/06 Won't compile as a DLL inside this struct 00065 // struct 00066 // { 00067 bool isValidPath; 00068 node_type rootNode; 00069 DataStructures::OrderedList<node_type, node_type> costMatrixIndices; 00070 weight_type *costMatrix; 00071 node_type *leastNodeArray; 00072 // } dijkstra; 00073 00074 struct NodeAndParent 00075 { 00076 DataStructures::Tree<node_type>*node; 00077 DataStructures::Tree<node_type>*parent; 00078 }; 00079 }; 00080 00081 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00082 WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph() 00083 { 00084 isValidPath=false; 00085 costMatrix=0; 00086 } 00087 00088 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00089 WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::~WeightedGraph() 00090 { 00091 Clear(); 00092 } 00093 00094 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00095 WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::WeightedGraph( const WeightedGraph& original_copy ) 00096 { 00097 adjacencyLists=original_copy.adjacencyLists; 00098 00099 isValidPath=original_copy.isValidPath; 00100 if (isValidPath) 00101 { 00102 rootNode=original_copy.rootNode; 00103 costMatrixIndices=original_copy.costMatrixIndices; 00104 costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ ); 00105 leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ ); 00106 memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type)); 00107 memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type)); 00108 } 00109 } 00110 00111 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00112 WeightedGraph<node_type, weight_type, allow_unlinkedNodes>& WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::operator=( const WeightedGraph& original_copy ) 00113 { 00114 adjacencyLists=original_copy.adjacencyLists; 00115 00116 isValidPath=original_copy.isValidPath; 00117 if (isValidPath) 00118 { 00119 rootNode=original_copy.rootNode; 00120 costMatrixIndices=original_copy.costMatrixIndices; 00121 costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(costMatrixIndices.Size() * costMatrixIndices.Size(), _FILE_AND_LINE_ ); 00122 leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(costMatrixIndices.Size(), _FILE_AND_LINE_ ); 00123 memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type)); 00124 memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type)); 00125 } 00126 00127 return *this; 00128 } 00129 00130 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00131 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddNode(const node_type &node) 00132 { 00133 adjacencyLists.SetNew(node, RakNet::OP_NEW<DataStructures::Map<node_type, weight_type> >( _FILE_AND_LINE_) ); 00134 } 00135 00136 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00137 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveNode(const node_type &node) 00138 { 00139 unsigned i; 00140 DataStructures::Queue<node_type> removeNodeQueue; 00141 00142 removeNodeQueue.Push(node, _FILE_AND_LINE_ ); 00143 while (removeNodeQueue.Size()) 00144 { 00145 RakNet::OP_DELETE(adjacencyLists.Pop(removeNodeQueue.Pop()), _FILE_AND_LINE_); 00146 00147 // Remove this node from all of the other lists as well 00148 for (i=0; i < adjacencyLists.Size(); i++) 00149 { 00150 adjacencyLists[i]->Delete(node); 00151 00152 #ifdef _MSC_VER 00153 #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant 00154 #endif 00155 if (allow_unlinkedNodes==false && adjacencyLists[i]->Size()==0) 00156 removeNodeQueue.Push(adjacencyLists.GetKeyAtIndex(i), _FILE_AND_LINE_ ); 00157 } 00158 } 00159 00160 ClearDijkstra(); 00161 } 00162 00163 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00164 bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::HasConnection(const node_type &node1, const node_type &node2) 00165 { 00166 if (node1==node2) 00167 return false; 00168 if (adjacencyLists.Has(node1)==false) 00169 return false; 00170 return adjacencyLists.Get(node1)->Has(node2); 00171 } 00172 00173 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00174 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::AddConnection(const node_type &node1, const node_type &node2, weight_type weight) 00175 { 00176 if (node1==node2) 00177 return; 00178 00179 if (adjacencyLists.Has(node1)==false) 00180 AddNode(node1); 00181 adjacencyLists.Get(node1)->Set(node2, weight); 00182 if (adjacencyLists.Has(node2)==false) 00183 AddNode(node2); 00184 adjacencyLists.Get(node2)->Set(node1, weight); 00185 } 00186 00187 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00188 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::RemoveConnection(const node_type &node1, const node_type &node2) 00189 { 00190 adjacencyLists.Get(node2)->Delete(node1); 00191 adjacencyLists.Get(node1)->Delete(node2); 00192 00193 #ifdef _MSC_VER 00194 #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant 00195 #endif 00196 if (allow_unlinkedNodes==false) // If we do not allow _unlinked nodes, then if there are no connections, remove the node 00197 { 00198 if (adjacencyLists.Get(node1)->Size()==0) 00199 RemoveNode(node1); // Will also remove node1 from the adjacency list of node2 00200 if (adjacencyLists.Has(node2) && adjacencyLists.Get(node2)->Size()==0) 00201 RemoveNode(node2); 00202 } 00203 00204 ClearDijkstra(); 00205 } 00206 00207 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00208 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Clear(void) 00209 { 00210 unsigned i; 00211 for (i=0; i < adjacencyLists.Size(); i++) 00212 RakNet::OP_DELETE(adjacencyLists[i], _FILE_AND_LINE_); 00213 adjacencyLists.Clear(); 00214 00215 ClearDijkstra(); 00216 } 00217 00218 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00219 bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetShortestPath(DataStructures::List<node_type> &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT) 00220 { 00221 path.Clear(false, _FILE_AND_LINE_); 00222 if (startNode==endNode) 00223 { 00224 path.Insert(startNode, _FILE_AND_LINE_); 00225 path.Insert(endNode, _FILE_AND_LINE_); 00226 return true; 00227 } 00228 00229 if (isValidPath==false || rootNode!=startNode) 00230 { 00231 ClearDijkstra(); 00232 GenerateDisjktraMatrix(startNode, INFINITE_WEIGHT); 00233 } 00234 00235 // return the results 00236 bool objectExists; 00237 unsigned col,row; 00238 weight_type currentWeight; 00239 DataStructures::Queue<node_type> outputQueue; 00240 col=costMatrixIndices.GetIndexFromKey(endNode, &objectExists); 00241 if (costMatrixIndices.Size()<2) 00242 { 00243 return false; 00244 } 00245 if (objectExists==false) 00246 { 00247 return false; 00248 } 00249 node_type vertex; 00250 row=costMatrixIndices.Size()-2; 00251 if (row==0) 00252 { 00253 path.Insert(startNode, _FILE_AND_LINE_); 00254 path.Insert(endNode, _FILE_AND_LINE_); 00255 return true; 00256 } 00257 currentWeight=costMatrix[row*adjacencyLists.Size() + col]; 00258 if (currentWeight==INFINITE_WEIGHT) 00259 { 00260 // No path 00261 return true; 00262 } 00263 vertex=endNode; 00264 outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_); 00265 row--; 00266 #ifdef _MSC_VER 00267 #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant 00268 #endif 00269 while (1) 00270 { 00271 while (costMatrix[row*adjacencyLists.Size() + col] == currentWeight) 00272 { 00273 if (row==0) 00274 { 00275 path.Insert(startNode, _FILE_AND_LINE_); 00276 for (col=0; outputQueue.Size(); col++) 00277 path.Insert(outputQueue.Pop(), _FILE_AND_LINE_); 00278 return true; 00279 } 00280 --row; 00281 } 00282 00283 vertex=leastNodeArray[row]; 00284 outputQueue.PushAtHead(vertex, 0, _FILE_AND_LINE_); 00285 if (row==0) 00286 break; 00287 col=costMatrixIndices.GetIndexFromKey(vertex, &objectExists); 00288 currentWeight=costMatrix[row*adjacencyLists.Size() + col]; 00289 } 00290 00291 path.Insert(startNode, _FILE_AND_LINE_); 00292 for (col=0; outputQueue.Size(); col++) 00293 path.Insert(outputQueue.Pop(), _FILE_AND_LINE_); 00294 return true; 00295 } 00296 00297 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00298 node_type WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeAtIndex(unsigned nodeIndex) const 00299 { 00300 return adjacencyLists.GetKeyAtIndex(nodeIndex); 00301 } 00302 00303 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00304 unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetNodeCount(void) const 00305 { 00306 return adjacencyLists.Size(); 00307 } 00308 00309 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00310 unsigned WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionCount(unsigned nodeIndex) const 00311 { 00312 return adjacencyLists[nodeIndex]->Size(); 00313 } 00314 00315 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00316 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const 00317 { 00318 outWeight=adjacencyLists[nodeIndex]->operator[](connectionIndex); 00319 outNode=adjacencyLists[nodeIndex]->GetKeyAtIndex(connectionIndex); 00320 } 00321 00322 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00323 bool WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GetSpanningTree(DataStructures::Tree<node_type> &outTree, DataStructures::List<node_type> *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT ) 00324 { 00325 // Find the shortest path from the start node to each of the input nodes. Add this path to a new WeightedGraph if the result is reachable 00326 DataStructures::List<node_type> path; 00327 DataStructures::WeightedGraph<node_type, weight_type, allow_unlinkedNodes> outGraph; 00328 bool res; 00329 unsigned i,j; 00330 for (i=0; i < inputNodes->Size(); i++) 00331 { 00332 res=GetShortestPath(path, startNode, (*inputNodes)[i], INFINITE_WEIGHT); 00333 if (res && path.Size()>0) 00334 { 00335 for (j=0; j < path.Size()-1; j++) 00336 { 00337 // Don't bother looking up the weight 00338 outGraph.AddConnection(path[j], path[j+1], INFINITE_WEIGHT); 00339 } 00340 } 00341 } 00342 00343 // Copy the graph to a tree. 00344 DataStructures::Queue<NodeAndParent> nodesToProcess; 00345 DataStructures::Tree<node_type> *current; 00346 DataStructures::Map<node_type, weight_type> *adjacencyList; 00347 node_type key; 00348 NodeAndParent nap, nap2; 00349 outTree.DeleteDecendants(); 00350 outTree.data=startNode; 00351 current=&outTree; 00352 if (outGraph.adjacencyLists.Has(startNode)==false) 00353 return false; 00354 adjacencyList = outGraph.adjacencyLists.Get(startNode); 00355 00356 for (i=0; i < adjacencyList->Size(); i++) 00357 { 00358 nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ ); 00359 nap2.node->data=adjacencyList->GetKeyAtIndex(i); 00360 nap2.parent=current; 00361 nodesToProcess.Push(nap2, _FILE_AND_LINE_ ); 00362 current->children.Insert(nap2.node, _FILE_AND_LINE_); 00363 } 00364 00365 while (nodesToProcess.Size()) 00366 { 00367 nap=nodesToProcess.Pop(); 00368 current=nap.node; 00369 adjacencyList = outGraph.adjacencyLists.Get(nap.node->data); 00370 00371 for (i=0; i < adjacencyList->Size(); i++) 00372 { 00373 key=adjacencyList->GetKeyAtIndex(i); 00374 if (key!=nap.parent->data) 00375 { 00376 nap2.node=RakNet::OP_NEW<DataStructures::Tree<node_type> >( _FILE_AND_LINE_ ); 00377 nap2.node->data=key; 00378 nap2.parent=current; 00379 nodesToProcess.Push(nap2, _FILE_AND_LINE_ ); 00380 current->children.Insert(nap2.node, _FILE_AND_LINE_); 00381 } 00382 } 00383 } 00384 00385 return true; 00386 } 00387 00388 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00389 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT) 00390 { 00391 if (adjacencyLists.Size()==0) 00392 return; 00393 00394 costMatrix = RakNet::OP_NEW_ARRAY<weight_type>(adjacencyLists.Size() * adjacencyLists.Size(), _FILE_AND_LINE_ ); 00395 leastNodeArray = RakNet::OP_NEW_ARRAY<node_type>(adjacencyLists.Size(), _FILE_AND_LINE_ ); 00396 00397 node_type currentNode; 00398 unsigned col, row, row2, openSetIndex; 00399 node_type adjacentKey; 00400 unsigned adjacentIndex; 00401 weight_type edgeWeight, currentNodeWeight, adjacentNodeWeight; 00402 DataStructures::Map<node_type, weight_type> *adjacencyList; 00403 DataStructures::Heap<weight_type, node_type, false> minHeap; 00404 DataStructures::Map<node_type, weight_type> openSet; 00405 00406 for (col=0; col < adjacencyLists.Size(); col++) 00407 { 00408 // This should be already sorted, so it's a bit inefficient to do an insertion sort, but what the heck 00409 costMatrixIndices.Insert(adjacencyLists.GetKeyAtIndex(col),adjacencyLists.GetKeyAtIndex(col), true, _FILE_AND_LINE_); 00410 } 00411 for (col=0; col < adjacencyLists.Size() * adjacencyLists.Size(); col++) 00412 costMatrix[col]=INFINITE_WEIGHT; 00413 currentNode=startNode; 00414 row=0; 00415 currentNodeWeight=0; 00416 rootNode=startNode; 00417 00418 // Clear the starting node column 00419 if (adjacencyLists.Size()) 00420 { 00421 adjacentIndex=adjacencyLists.GetIndexAtKey(startNode); 00422 for (row2=0; row2 < adjacencyLists.Size(); row2++) 00423 costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=0; 00424 } 00425 00426 while (row < adjacencyLists.Size()-1) 00427 { 00428 adjacencyList = adjacencyLists.Get(currentNode); 00429 // Go through all connections from the current node. If the new weight is less than the current weight, then update that weight. 00430 for (col=0; col < adjacencyList->Size(); col++) 00431 { 00432 edgeWeight=(*adjacencyList)[col]; 00433 adjacentKey=adjacencyList->GetKeyAtIndex(col); 00434 adjacentIndex=adjacencyLists.GetIndexAtKey(adjacentKey); 00435 adjacentNodeWeight=costMatrix[row*adjacencyLists.Size() + adjacentIndex]; 00436 00437 if (currentNodeWeight + edgeWeight < adjacentNodeWeight) 00438 { 00439 // Update the weight for the adjacent node 00440 for (row2=row; row2 < adjacencyLists.Size(); row2++) 00441 costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=currentNodeWeight + edgeWeight; 00442 openSet.Set(adjacentKey, currentNodeWeight + edgeWeight); 00443 } 00444 } 00445 00446 // Find the lowest in the open set 00447 minHeap.Clear(true,_FILE_AND_LINE_); 00448 for (openSetIndex=0; openSetIndex < openSet.Size(); openSetIndex++) 00449 minHeap.Push(openSet[openSetIndex], openSet.GetKeyAtIndex(openSetIndex),_FILE_AND_LINE_); 00450 00451 /* 00452 unsigned i,j; 00453 for (i=0; i < adjacencyLists.Size()-1; i++) 00454 { 00455 for (j=0; j < adjacencyLists.Size(); j++) 00456 { 00457 RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]); 00458 } 00459 RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]); 00460 RAKNET_DEBUG_PRINTF("\n"); 00461 } 00462 */ 00463 00464 if (minHeap.Size()==0) 00465 { 00466 // Unreachable nodes 00467 isValidPath=true; 00468 return; 00469 } 00470 00471 currentNodeWeight=minHeap.PeekWeight(0); 00472 leastNodeArray[row]=minHeap.Pop(0); 00473 currentNode=leastNodeArray[row]; 00474 openSet.Delete(currentNode); 00475 row++; 00476 } 00477 00478 /* 00479 #ifdef _DEBUG 00480 unsigned i,j; 00481 for (i=0; i < adjacencyLists.Size()-1; i++) 00482 { 00483 for (j=0; j < adjacencyLists.Size(); j++) 00484 { 00485 RAKNET_DEBUG_PRINTF("%2i ", costMatrix[i*adjacencyLists.Size() + j]); 00486 } 00487 RAKNET_DEBUG_PRINTF("Node=%i", leastNodeArray[i]); 00488 RAKNET_DEBUG_PRINTF("\n"); 00489 } 00490 #endif 00491 */ 00492 00493 isValidPath=true; 00494 } 00495 00496 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00497 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::ClearDijkstra(void) 00498 { 00499 if (isValidPath) 00500 { 00501 isValidPath=false; 00502 RakNet::OP_DELETE_ARRAY(costMatrix, _FILE_AND_LINE_); 00503 RakNet::OP_DELETE_ARRAY(leastNodeArray, _FILE_AND_LINE_); 00504 costMatrixIndices.Clear(false, _FILE_AND_LINE_); 00505 } 00506 } 00507 00508 template <class node_type, class weight_type, bool allow_unlinkedNodes> 00509 void WeightedGraph<node_type, weight_type, allow_unlinkedNodes>::Print(void) 00510 { 00511 #ifdef _DEBUG 00512 unsigned i,j; 00513 for (i=0; i < adjacencyLists.Size(); i++) 00514 { 00515 //RAKNET_DEBUG_PRINTF("%i connected to ", i); 00516 RAKNET_DEBUG_PRINTF("%s connected to ", adjacencyLists.GetKeyAtIndex(i).systemAddress.ToString()); 00517 00518 if (adjacencyLists[i]->Size()==0) 00519 RAKNET_DEBUG_PRINTF("<Empty>"); 00520 else 00521 { 00522 for (j=0; j < adjacencyLists[i]->Size(); j++) 00523 // RAKNET_DEBUG_PRINTF("%i (%.2f) ", adjacencyLists.GetIndexAtKey(adjacencyLists[i]->GetKeyAtIndex(j)), (float) adjacencyLists[i]->operator[](j) ); 00524 RAKNET_DEBUG_PRINTF("%s (%.2f) ", adjacencyLists[i]->GetKeyAtIndex(j).systemAddress.ToString(), (float) adjacencyLists[i]->operator[](j) ); 00525 } 00526 00527 RAKNET_DEBUG_PRINTF("\n"); 00528 } 00529 #endif 00530 } 00531 } 00532 00533 #ifdef _MSC_VER 00534 #pragma warning( pop ) 00535 #endif 00536 00537 #endif
Copyright © 2007-2010 by The Shadowrun: Awakened Team. This work is licensed under the GNU Lesser General Public License 3.