PageRenderTime 61ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/JTacticalSim.Service/RulesService.cs

https://github.com/Queztionmark/JTacticalSim
C# | 1401 lines | 1014 code | 274 blank | 113 comment | 165 complexity | f414a41830a8cdd2f6344b272033631c MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.ServiceModel;
  7. using LinqKit;
  8. using System.Text.RegularExpressions;
  9. using System.Threading.Tasks;
  10. using JTacticalSim.API;
  11. using JTacticalSim.API.InfoObjects;
  12. using JTacticalSim.API.Component;
  13. using JTacticalSim.API.Game;
  14. using JTacticalSim.API.Service;
  15. using JTacticalSim.Service;
  16. using JTacticalSim.DataContext;
  17. using JTacticalSim.Utility;
  18. namespace JTacticalSim.Service
  19. {
  20. [ServiceBehavior]
  21. public class RulesService : BaseGameService, IRulesService
  22. {
  23. static readonly object padlock = new object();
  24. private static volatile IRulesService _instance;
  25. public static IRulesService Instance
  26. {
  27. get
  28. {
  29. if (_instance == null)
  30. {
  31. lock (padlock)
  32. if (_instance == null) _instance = new RulesService();
  33. }
  34. return _instance;
  35. }
  36. }
  37. private RulesService()
  38. {}
  39. #region Service Methods
  40. // Node/Tile
  41. [OperationBehavior]
  42. public IServiceResult<bool> NodeIsFactionNode(ICoordinate location, IFaction faction)
  43. {
  44. var r = new ServiceResult<bool>();
  45. var existingNode = JTSServices.NodeService.GetNodeAt(location);
  46. if (existingNode.Faction.Equals(faction))
  47. r.Result = true;
  48. return r;
  49. }
  50. [OperationBehavior]
  51. public IServiceResult<bool> NodeIsValidForMove(IUnit unit, INode targetNode)
  52. {
  53. var r = new ServiceResult<bool>{Status = ResultStatus.SUCCESS, Result = true};
  54. try
  55. {
  56. var sourceNode = unit.GetNode();
  57. var allPossibleNodes = JTSServices.NodeService.GetAllNodesWithinDistance(sourceNode, unit.CurrentMoveStats.MovementPoints, true, false).ToList();
  58. if (!allPossibleNodes.Any(n => n.Equals(targetNode)))
  59. {
  60. r.Status = ResultStatus.OTHER;
  61. r.Messages.Add("Node is not in movement range for {0}".F(unit.Name));
  62. r.Result = false;
  63. return r;
  64. }
  65. // Remove any nodes with default tiles that prohibit movement for this unit based on geography
  66. allPossibleNodes.RemoveAll(n => !JTSServices.RulesService.TileIsAllowableForUnit(unit, n.DefaultTile()).Result);
  67. var path = JTSServices.AIService.FindPath(sourceNode, targetNode, allPossibleNodes, unit);
  68. if (path.Result == null)
  69. {
  70. r.Messages.Add("Node is not a valid move for {0}".F(unit.Name));
  71. r.Result = false;
  72. return r;
  73. }
  74. }
  75. catch (Exception ex)
  76. {
  77. r.Status = ResultStatus.EXCEPTION;
  78. r.Result = false;
  79. r.ex = ex;
  80. return r;
  81. }
  82. r.Messages.Add("Node is a valid move for {0}".F(unit.Name));
  83. return r;
  84. }
  85. [OperationBehavior]
  86. public IServiceResult<bool> NodeIsChokepoint(INode node)
  87. {
  88. var r = new ServiceResult<bool> { Status = ResultStatus.SUCCESS, Result = false};
  89. List<Tuple<ITile, ITile>> neighborPairs;
  90. // Bridge
  91. var isBridge = node.DefaultTile().Infrastructure.Any(d => d.IsDemographicClass("bridge"));
  92. if (isBridge)
  93. {
  94. r.Result = true;
  95. r.Messages.Add("Node has a bridge.");
  96. return r;
  97. }
  98. // Get all the surrounding node tiles in opposite pairs.
  99. // We determine if this is a chokepoint by ascertaining whether there is ANY opposite pair
  100. // That would force units to use this node in a path
  101. try
  102. {
  103. neighborPairs = JTSServices.NodeService.GetNeighborNodesOppositeTilePairs(node);
  104. }
  105. catch (Exception ex)
  106. {
  107. r.Status = ResultStatus.EXCEPTION;
  108. r.ex = ex;
  109. return r;
  110. }
  111. try
  112. {
  113. var pathThroughRestrictedMovementResult = TileIsPassThroughRestrictedMovement(node.DefaultTile(), neighborPairs);
  114. if (pathThroughRestrictedMovementResult.Status == ResultStatus.EXCEPTION) throw pathThroughRestrictedMovementResult.ex;
  115. if (pathThroughRestrictedMovementResult.Result) return pathThroughRestrictedMovementResult;
  116. var narrowGeogResult = TileIsNarrowGeography(node.DefaultTile(), neighborPairs);
  117. if (narrowGeogResult.Status == ResultStatus.EXCEPTION) throw narrowGeogResult.ex;
  118. if (narrowGeogResult.Result) return narrowGeogResult;
  119. }
  120. catch (Exception ex)
  121. {
  122. r.Status = ResultStatus.EXCEPTION;
  123. r.ex = ex;
  124. return r;
  125. }
  126. // Is not a chokepoint
  127. r.Messages.Add("Node is not a chokepoint.");
  128. return r;
  129. }
  130. [OperationBehavior]
  131. public IServiceResult<bool> TileIsPassThroughRestrictedMovement(ITile tile, List<Tuple<ITile, ITile>> neighborPairs)
  132. {
  133. var r = new ServiceResult<bool> { Status = ResultStatus.SUCCESS, Result = false };
  134. // Mountain Pass/ or pass through any other restricted movement areas
  135. try
  136. {
  137. var isPathThroughRestrictedMovement = neighborPairs.Any(p => (tile.GetNetMovementAdjustment() >= 0) // If the source is restricted, then there is no path
  138. && // One is null. The other has movement restrictions
  139. ((p.Item1 == null && p.Item2 != null) && (p.Item2.GetNetMovementAdjustment() < 0)
  140. ||
  141. (p.Item2 == null && p.Item1 != null) && (p.Item1.GetNetMovementAdjustment() < 0))
  142. || // Neither are null and both have restricted movement
  143. (p.Item1 != null && p.Item2 != null) && (p.Item1.GetNetMovementAdjustment() < 0 && p.Item2.GetNetMovementAdjustment() < 0));
  144. if (isPathThroughRestrictedMovement)
  145. {
  146. r.Result = true;
  147. r.Messages.Add("Tile allows for movement through otherwise movement restricted area.");
  148. return r;
  149. }
  150. return r;
  151. }
  152. catch (Exception ex)
  153. {
  154. r.Status = ResultStatus.EXCEPTION;
  155. r.ex = ex;
  156. return r;
  157. }
  158. }
  159. [OperationBehavior]
  160. public IServiceResult<bool> TileIsNarrowGeography(ITile tile, List<Tuple<ITile, ITile>> neighborPairs)
  161. {
  162. var r = new ServiceResult<bool> { Status = ResultStatus.SUCCESS, Result = false};
  163. // Narrow Land/Waterway (base geog)
  164. // Either of the pair tiles' base geography can not match the source node
  165. // The pair tiles MUST match each other. One can be a hybrid.
  166. try
  167. {
  168. var isNarrowGeog = neighborPairs.Any(p => (p.Item1 == null && p.Item2 == null) // both are null - corner node
  169. || // One is null and the other is either a hybrid or does not match the source
  170. (p.Item1 == null && p.Item2 != null) &&
  171. (p.Item2.BaseGeography.Any(g => g.IsHybrid()) || !p.Item2.BaseGeography.Any(g => tile.BaseGeography.Contains(g)))
  172. ||
  173. (p.Item2 == null && p.Item1 != null) &&
  174. (p.Item1.BaseGeography.Any(g => g.IsHybrid()) || !p.Item1.BaseGeography.Any(g => tile.BaseGeography.Contains(g)))
  175. || // Neither are null and both opposites are hybrids
  176. (p.Item1 != null && p.Item2 != null) &&
  177. p.Item2.BaseGeography.Any(g => g.IsHybrid()) && p.Item1.BaseGeography.Any(g => g.IsHybrid())
  178. || // Neither are null and neither match the source
  179. (p.Item1 != null && p.Item2 != null) &&
  180. (p.Item1.BaseGeography.Any(g => g.IsHybrid()) || !p.Item1.BaseGeography.Any(g => tile.BaseGeography.Contains(g))) &&
  181. (p.Item2.BaseGeography.Any(g => g.IsHybrid()) || !p.Item2.BaseGeography.Any(g => tile.BaseGeography.Contains(g))));
  182. if (isNarrowGeog)
  183. {
  184. r.Result = true;
  185. r.Messages.Add("Tile geography is narrow.");
  186. return r;
  187. }
  188. return r;
  189. }
  190. catch (Exception ex)
  191. {
  192. r.Status = ResultStatus.EXCEPTION;
  193. r.ex = ex;
  194. return r;
  195. }
  196. }
  197. [OperationBehavior]
  198. public IServiceResult<bool> TileIsAllowableForUnit(IUnit unit, ITile tile)
  199. {
  200. var r = new ServiceResult<bool>{Status = ResultStatus.SUCCESS, Result = true};
  201. var rGeog = TileIsAllowableForUnitType(unit.UnitInfo.UnitType, tile);
  202. var rOverride = TileHasMovementOverrideForUnitType(unit.UnitInfo.UnitType, tile);
  203. r.Result = rGeog.Result || rOverride.Result;
  204. return r;
  205. }
  206. [OperationBehavior]
  207. public IServiceResult<bool> TileIsAllowableForUnitType(IUnitType unitType, ITile tile)
  208. {
  209. var r = new ServiceResult<bool>{Status = ResultStatus.SUCCESS, Result = true};
  210. try
  211. {
  212. var tileGeogTypeIDs = tile.AllGeography.Select(g => g.DemographicClass.ID);
  213. var unitGeogTypeIDs = JTSServices.DataService.LookupUnitGeogTypesByBaseTypes(new[] {unitType.BaseType.ID});
  214. var demoClassIDs = JTSServices.DataService.LookupDemographicClassesByUnitGeogTypes(unitGeogTypeIDs);
  215. // Tile has at least one geographic demographic compatible with this unit, a movement override or no geographic demos
  216. r.Result = demoClassIDs.Any(dID => tileGeogTypeIDs.Contains(dID)) ||
  217. demoClassIDs.Any(dID => dID == 9999) || // Applies to all records in a table
  218. tileGeogTypeIDs.Count() == 0;
  219. return r;
  220. }
  221. catch (Exception ex)
  222. {
  223. r.Status = ResultStatus.EXCEPTION;
  224. r.ex = ex;
  225. r.Result = false;
  226. return r;
  227. }
  228. }
  229. [OperationBehavior]
  230. public IServiceResult<bool> TileHasMovementOverrideForUnit(IUnit unit, ITile tile)
  231. {
  232. var r = TileHasMovementOverrideForUnitType(unit.UnitInfo.UnitType, tile);
  233. return r;
  234. }
  235. [OperationBehavior]
  236. public IServiceResult<bool> TileHasMovementOverrideForUnitType(IUnitType unitType, ITile tile)
  237. {
  238. var r = new ServiceResult<bool>{Status = ResultStatus.SUCCESS, Result = true};
  239. try
  240. {
  241. var unitGeogTypeIDs = JTSServices.DataService.LookupUnitGeogTypesByBaseTypes(new [] {unitType.BaseType.ID});
  242. var tileGeogTypeIDs = tile.AllGeography.Select(g => g.DemographicClass.ID);
  243. var tileInfraTypeIDs = tile.Infrastructure.Select(i => i.DemographicClass.ID);
  244. var overrides = DataRepository.GetUnitGeogTypeMovementOverrides();
  245. // Global override
  246. if (overrides.Any(ov => Enumerable.Contains(unitGeogTypeIDs, ov.UnitGeogType) &&
  247. (ov.Geography == 9999 & ov.Infrastructure == 9999)))
  248. {
  249. r.Result = true;
  250. return r;
  251. }
  252. var q = from ov in overrides
  253. join ugt in unitGeogTypeIDs on ov.UnitGeogType equals ugt
  254. join tgt in tileGeogTypeIDs on ov.Geography equals tgt
  255. join tit in tileInfraTypeIDs on ov.Infrastructure equals tit
  256. select new {ov.UnitGeogType, ov.Geography, ov.Infrastructure};
  257. r.Result = q.Any();
  258. }
  259. catch (Exception ex)
  260. {
  261. r.Status = ResultStatus.EXCEPTION;
  262. r.ex = ex;
  263. r.Result = false;
  264. return r;
  265. }
  266. return r;
  267. }
  268. // Unit
  269. [OperationBehavior]
  270. public IServiceResult<int> GetAllowableDeployDistanceForTransport(IUnit transport)
  271. {
  272. var r = new ServiceResult<int> {Status = ResultStatus.SUCCESS};
  273. try
  274. {
  275. r.Result = (transport.IsUnitBaseType("Watercraft") || transport.IsUnitBaseType("Watercraft-sub")) ? 1 : 0;
  276. }
  277. catch (Exception ex)
  278. {
  279. r.Status = ResultStatus.EXCEPTION;
  280. r.ex = ex;
  281. }
  282. return r;
  283. }
  284. [OperationBehavior]
  285. public IServiceResult<int> GetAllowableLoadDistanceForTransport(IUnit transport)
  286. {
  287. var r = new ServiceResult<int> {Status = ResultStatus.SUCCESS};
  288. try
  289. {
  290. r.Result = (transport.IsUnitBaseType("Watercraft") || transport.IsUnitBaseType("Watercraft-sub")) ? 1 : 0;
  291. }
  292. catch (Exception ex)
  293. {
  294. r.Status = ResultStatus.EXCEPTION;
  295. r.ex = ex;
  296. }
  297. return r;
  298. }
  299. [OperationBehavior]
  300. public IServiceResult<bool> UnitNameIsUnique(string name)
  301. {
  302. var r = new ServiceResult<bool>();
  303. r.Result = ComponentRepository.GetUnits().All(m => m.Name.ToLowerInvariant() != name.ToLowerInvariant());
  304. return r;
  305. }
  306. [OperationBehavior]
  307. public IServiceResult<bool> UnitsCanDoBattleWithUnits(List<IUnit> units, List<IUnit> opponents, BattleType battleType)
  308. {
  309. var r = new ServiceResult<bool> { Status = ResultStatus.SUCCESS };
  310. r.Result = false;
  311. units.ForEach(u => opponents.ForEach(o =>
  312. {
  313. if (UnitCanDoBattleWithUnit(u, o, battleType).Result)
  314. {
  315. r.Result = true;
  316. return;
  317. }
  318. }));
  319. return r;
  320. }
  321. [OperationBehavior]
  322. public IServiceResult<bool> UnitCanDoBattleWithUnit(IUnit unit, IUnit opponent, BattleType battleType)
  323. {
  324. var r = new ServiceResult<bool>();
  325. // This isn't actually necessary as local battles will always have a distance of 0
  326. // I'm leaving this in here to
  327. // a) as a gate to save some processing time
  328. // b) assist in testing where the sim may be assembling units at different locations to simulate a local battle
  329. if (battleType == BattleType.BARRAGE)
  330. {
  331. // Check distance first. Local battles should all pass with distance of 0
  332. // else, opponent (target) needs to be within the range of the remaining remote fire points for the attacker (source)
  333. var distanceResult = JTSServices.AIService.CalculateNodeCountToUnit(unit, opponent, unit.CurrentMoveStats.RemoteFirePoints);
  334. if (distanceResult.Status == ResultStatus.FAILURE)
  335. {
  336. r.Result = false;
  337. r.Messages.Add(distanceResult.Message);
  338. return r;
  339. }
  340. }
  341. // Within range, check other rules
  342. IEnumerable<int> battleAllowedGeogTypeIDs = DataRepository.GetUnitBattleEffectiveLookup()
  343. .Where(ube => ube.UnitType == unit.UnitInfo.UnitType.ID)
  344. .Select(ube => Convert.ToInt32(ube.UnitGeogType as int?));
  345. var defenderGeogTypes = JTSServices.DataService.LookupUnitGeogTypesByBaseTypes(new[] {opponent.UnitInfo.UnitType.BaseType.ID});
  346. r.Result = battleAllowedGeogTypeIDs.Any(id => defenderGeogTypes.Contains(id)) ||
  347. battleAllowedGeogTypeIDs.Any(id => defenderGeogTypes.Contains(9999)) ||
  348. battleAllowedGeogTypeIDs.Any(id => id == 9999);
  349. return r;
  350. }
  351. [OperationBehavior]
  352. public IServiceResult<bool> UnitCanDoBattle(IUnit unit)
  353. {
  354. var r = new ServiceResult<bool>();
  355. r.Result = ((unit.CurrentMoveStats.RemoteFirePoints > 0) ||
  356. !unit.CurrentMoveStats.HasPerformedAction);
  357. return r;
  358. }
  359. [OperationBehavior]
  360. public IServiceResult<bool> UnitCanClaimNodeForFaction(IUnit unit)
  361. {
  362. //For now, unit must only not be an air unit
  363. var r = new ServiceResult<bool>();
  364. r.Result = (!unit.IsUnitBaseType("Plane") && !unit.IsUnitBaseType("Helicopter"));
  365. return r;
  366. }
  367. [OperationBehavior]
  368. public IServiceResult<bool> UnitCanAttachToUnit(IUnit unit, IUnit attachToUnit)
  369. {
  370. var r = new ServiceResult<bool>();
  371. r.Status = ResultStatus.SUCCESS;
  372. // First rule : only attach to units at the next highest group type level
  373. r.Result = attachToUnit.UnitInfo.UnitGroupType.Equals(unit.UnitInfo.UnitGroupType.NextHighestGroupType);
  374. if (!r.Result)
  375. {
  376. r.Status = ResultStatus.FAILURE;
  377. r.Messages.Add("{0} can not be attached to {1} due to it's organization level.".F(unit.Name, attachToUnit.Name));
  378. return r;
  379. }
  380. return r;
  381. }
  382. [OperationBehavior]
  383. public IServiceResult<bool> UnitIsSupplied(IUnit unit)
  384. {
  385. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  386. try
  387. {
  388. r.Result = (JTSServices.AIService.FindSupplyPath(unit.GetNode(), unit.Faction, BaseGamePointValues.MaxSupplyDistance) != null);
  389. }
  390. catch (Exception ex)
  391. {
  392. r.Status = ResultStatus.EXCEPTION;
  393. r.ex = ex;
  394. }
  395. return r;
  396. }
  397. [OperationBehavior]
  398. public IServiceResult<bool> UnitHasMedicalSupport(IUnit unit)
  399. {
  400. // Should only be determined based on medical unit AT current node including current unit
  401. // Aircraft that can not land should not get medical bonus unless it is medical unit class
  402. var r = new ServiceResult<bool> {Result = true, Status = ResultStatus.SUCCESS};
  403. // Check the current unit first to save some processing
  404. if (unit.IsUnitClass("medical"))
  405. {
  406. r.Messages.Add("{0} is a medical unit.".F(unit.Name));
  407. return r;
  408. }
  409. if (!unit.UnitInfo.UnitType.BaseType.CanReceiveMedicalSupport)
  410. {
  411. r.Result = false;
  412. r.Messages.Add("{0} has a base type that can not receive medical suppport.".F(unit.Name));
  413. return r;
  414. }
  415. var units = JTSServices.UnitService.GetAllUnits(new[] {unit.Faction})
  416. .Where(u => u.Location != null &&
  417. u.LocationEquals(unit.Location));
  418. r.Result = units.Any(u => u.IsUnitClass("medical"));
  419. return r;
  420. }
  421. [OperationBehavior]
  422. public IServiceResult<bool> UnitIsUnitClass(IUnit unit, string className)
  423. {
  424. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  425. try
  426. {
  427. var uc = JTSServices.UnitService.GetUnitClassByName(className);
  428. r.Result = (unit.UnitInfo.UnitClass.Equals(uc));
  429. }
  430. catch (ComponentNotFoundException ex)
  431. {
  432. r.Status = ResultStatus.EXCEPTION;
  433. r.ex = ex;
  434. return r;
  435. }
  436. return r;
  437. }
  438. [OperationBehavior]
  439. public IServiceResult<bool> UnitIsUnitBaseType(IUnit unit, string baseTypeName)
  440. {
  441. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  442. try
  443. {
  444. var baseType = ComponentRepository.GetUnitBaseTypes()
  445. .Single(ubt => ubt.Name.ToLowerInvariant() == baseTypeName.ToLowerInvariant())
  446. .ToComponent();
  447. if (baseTypeName == null)
  448. throw new ComponentNotFoundException("UnitBaseType '{0}' not found.");
  449. r.Result = (unit.UnitInfo.UnitType.BaseType.Equals(baseType));
  450. }
  451. catch (ComponentNotFoundException ex)
  452. {
  453. r.Status = ResultStatus.EXCEPTION;
  454. r.ex = ex;
  455. }
  456. return r;
  457. }
  458. [OperationBehavior]
  459. public IServiceResult<bool> UnitIsDeployableToNode(IUnit unit, INode node)
  460. {
  461. // Node must be compatible with the unit
  462. // Unit must have the 1 remaining movement point to move
  463. return JTSServices.RulesService.NodeIsValidForMove(unit, node);
  464. }
  465. [OperationBehavior]
  466. public IServiceResult<bool> UnitCanReinforceAtLocation(IUnit unit, INode node)
  467. {
  468. // Node must be compatible with the unit
  469. // Node must be a friendly
  470. // There must be an HQ for the player at the location
  471. var r = new ServiceResult<bool>
  472. {
  473. Result = true,
  474. Status = ResultStatus.SUCCESS
  475. };
  476. var playerUnits = node.GetAllUnits().Where(u => u.Country.Equals(unit.Country));
  477. var tileValidResult = JTSServices.RulesService.TileIsAllowableForUnit(unit, node.DefaultTile());
  478. if (!tileValidResult.Result)
  479. {
  480. return tileValidResult;
  481. }
  482. if (!node.IsFriendly())
  483. {
  484. r.Result = false;
  485. r.Status = ResultStatus.FAILURE;
  486. r.Messages.Add("Node is not friendly.");
  487. return r;
  488. }
  489. //if (!playerUnits.Any())
  490. //{
  491. // r.Status = ResultStatus.FAILURE;
  492. // r.Message = "Location {0} has no units for {1}.".F(node.Location.ToStringForName(), unit.Country.Name);
  493. // r.Result = false;
  494. // return r;
  495. //}
  496. // HQs can be placed on any friendly space with units existing - FYI
  497. if (!playerUnits.Any(u => JTSServices.RulesService.UnitIsUnitClass(u, "HQ").Result) &&
  498. !JTSServices.RulesService.UnitIsUnitClass(unit, "HQ").Result)
  499. {
  500. r.Status = ResultStatus.FAILURE;
  501. r.Messages.Add("Location {0} has no HQ for {1}.".F(node.Location.ToStringForName(), unit.Country.Name));
  502. r.Result = false;
  503. return r;
  504. }
  505. r.Messages.Add("{0} is deployable to {1}".F(unit.Name, node.Location.ToStringForName()));
  506. return r;
  507. }
  508. [OperationBehavior]
  509. public IServiceResult<bool> UnitCanTransportUnitTypeAndClass(IUnit transport, IUnit unit)
  510. {
  511. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  512. r.Result = DataRepository.GetUnitTransportUnitTypeUnitClasses().Any(te => (te.TransportUnitType == transport.UnitInfo.UnitType.ID &&
  513. (te.CarriedUnitType == unit.UnitInfo.UnitType.ID || te.CarriedUnitType == 9999) &&
  514. (te.CarriedUnitClass == unit.UnitInfo.UnitClass.ID || te.CarriedUnitClass == 9999)));
  515. return r;
  516. }
  517. [OperationBehavior]
  518. public IServiceResult<bool> UnitCanMoveInDirection(IUnit unit, Direction direction)
  519. {
  520. var r = new ServiceResult<bool> { Status = ResultStatus.SUCCESS, Result = false };
  521. var unitGeog = JTSServices.DataService.LookupUnitGeogTypesByBaseTypes(new [] {unit.UnitInfo.UnitType.BaseType.ID});
  522. var node = unit.GetNode();
  523. INode targetNode = null;
  524. try
  525. {
  526. targetNode = node.GetNodeInDirection(direction, 1);
  527. }
  528. catch (ComponentNotFoundException)
  529. {}
  530. var canExitCurrentNode = true;
  531. var canEnterTargetNode = true;
  532. // We only want to be checking this for hybrid demographics
  533. // Do check on demographics first
  534. var currentHybridDemographicTypes = node.DefaultTile().GetAllHybridDemographics().Select(d => d.DemographicClass.ID);
  535. canExitCurrentNode = !DataRepository.GetMovementHinderanceInDirection()
  536. .Any(h => ( (Enumerable.Contains(currentHybridDemographicTypes, h.DemographicClass)) &&
  537. (Enumerable.Contains(unitGeog, h.UnitGeogType) || h.UnitGeogType == 9999) &&
  538. (h.Direction == direction)));
  539. // Then Check for overrides
  540. if (!canExitCurrentNode)
  541. canExitCurrentNode = JTSServices.RulesService.TileHasMovementOverrideForUnit(unit, node.DefaultTile()).Result;
  542. if (targetNode != null) //We're assumed to have reached the board edge... no target node
  543. {
  544. var targetHybridDemographics = targetNode.DefaultTile().GetAllHybridDemographics().Select(d => d.DemographicClass.ID);
  545. canEnterTargetNode = !DataRepository.GetMovementHinderanceInDirection()
  546. .Any(h => ( (Enumerable.Contains(targetHybridDemographics, h.DemographicClass)) &&
  547. (Enumerable.Contains(unitGeog, h.UnitGeogType) || h.UnitGeogType == 9999) &&
  548. (h.Direction == Orienting.GetOppositeDirection(direction))));
  549. // Then Check for overrides
  550. if (!canEnterTargetNode)
  551. canEnterTargetNode = JTSServices.RulesService.TileHasMovementOverrideForUnit(unit, targetNode.DefaultTile()).Result;
  552. }
  553. else
  554. {
  555. canEnterTargetNode = false;
  556. }
  557. r.Result = (canExitCurrentNode && canEnterTargetNode);
  558. //retVal.Result = canExitCurrentNode;
  559. return r;
  560. }
  561. [OperationBehavior]
  562. public IServiceResult<bool> UnitCanPerformTask(IUnit unit, IUnitTask task)
  563. {
  564. // Based on allowable task assignments for a UnitClass and UnitGroupType
  565. var r = new ServiceResult<bool>() { Status = ResultStatus.SUCCESS, Result = false};
  566. bool unitClassCanPerformTask = false;
  567. bool unitGroupTypeCanPerformTask = false;
  568. try
  569. {
  570. unitClassCanPerformTask = DataRepository.GetUnitTaskUnitClassesLookup()
  571. .Any(utuc => utuc.UnitTask == task.ID &&
  572. (utuc.UnitClass == unit.UnitInfo.UnitClass.ID || utuc.UnitClass == 9999));
  573. unitGroupTypeCanPerformTask = DataRepository.GetUnitGroupTypeUnitTaskLookup()
  574. .Any(ugtut => ugtut.UnitTask == task.ID &&
  575. (ugtut.UnitGroupType == unit.UnitInfo.UnitGroupType.ID || ugtut.UnitGroupType == 9999));
  576. if (!unitClassCanPerformTask)
  577. {
  578. r.Messages.Add("{0} can not perform task {1} due to its unit class.".F(unit.Name, task.Name));
  579. return r;
  580. }
  581. if (!unitGroupTypeCanPerformTask)
  582. {
  583. r.Messages.Add("{0} can not perform task {1} due to its unit group type.".F(unit.Name, task.Name));
  584. return r;
  585. }
  586. r.Result = true;
  587. r.Messages.Add("{0} is allowable for {1}.".F(task.Name, unit.Name));
  588. return r;
  589. }
  590. catch (Exception ex)
  591. {
  592. r.Status = ResultStatus.EXCEPTION;
  593. r.ex = ex;
  594. return r;
  595. }
  596. }
  597. [OperationBehavior]
  598. public IServiceResult<bool> ComponentIsVisible(IMoveableComponent component)
  599. {
  600. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  601. r.Result = (!component.IsBeingTransported());
  602. return r;
  603. }
  604. [OperationBehavior]
  605. public IServiceResult<bool> ComponentIsBeingTransported(IMoveableComponent component)
  606. {
  607. var r = new ServiceResult<bool> {Result = false, Status = ResultStatus.SUCCESS};
  608. r.Result = DataRepository.GetUnitTransports().Any(ut => ut.Unit == component.ID);
  609. return r;
  610. }
  611. // Game
  612. [OperationBehavior]
  613. public IServiceResult<bool> GameVictoryAchieved(IFaction faction)
  614. {
  615. var conditions = faction.VictoryConditions();
  616. var r = new ServiceResult<bool>{Status = ResultStatus.SUCCESS, Result = false};
  617. if (conditions == null || conditions.Count == 0)
  618. throw new Exception("{0} has no victory condition and can not win the game.".F(faction.Name));
  619. // Check each condition based on its own set of rules
  620. foreach(var vc in conditions)
  621. {
  622. switch (vc.ConditionType)
  623. {
  624. case GameVictoryCondition.ENEMY_UNITS_REMAINING :
  625. {
  626. var enemyFactions = ComponentRepository.GetFactions()
  627. .Where(f => !f.ToComponent().Equals(faction))
  628. .Select(f => f.ToComponent());
  629. var enemyUnits = JTSServices.UnitService.GetAllUnits(enemyFactions);
  630. r.Result = enemyUnits.Count() <= vc.Value;
  631. // Condition met... we're good
  632. if (r.Result) return r;
  633. break;
  634. }
  635. case GameVictoryCondition.VICTORY_POINTS_HELD :
  636. {
  637. r.Result = faction.GetCurrentVictoryPoints() >= vc.Value;
  638. // Condition met... we're good
  639. if (r.Result) return r;
  640. break;
  641. }
  642. case GameVictoryCondition.FLAG_CAPTURED :
  643. {
  644. var enemyFactions = ComponentRepository.GetFactions()
  645. .Where(f => !f.ToComponent().Equals(faction))
  646. .Select(f => f.ToComponent());
  647. // Get all the remaining flag-holders for all factions
  648. var enemyUnits = JTSServices.UnitService.GetAllUnits(enemyFactions)
  649. .Where(u => u.ID == vc.Value);
  650. // if there are none, all flags are captured
  651. r.Result = !enemyUnits.Any();
  652. // Condition met
  653. if (r.Result) return r;
  654. break;
  655. }
  656. }
  657. }
  658. // No victory conditions met
  659. return r;
  660. }
  661. [OperationBehavior]
  662. public IServiceResult<bool> GameTitleIsValid(string title)
  663. {
  664. var r = new ServiceResult<bool>
  665. {
  666. Status = ResultStatus.SUCCESS,
  667. Result = true
  668. };
  669. var reg = new Regex("^[-_0-9a-zA-Z]*$");
  670. var nameInUse = false;
  671. if (title.IndexOfAny(new[] {' '}) > 0)
  672. {
  673. r.Status = ResultStatus.FAILURE;
  674. r.Messages.Add("Title contains spaces.");
  675. r.Result = false;
  676. return r;
  677. }
  678. if (!reg.IsMatch(title))
  679. {
  680. r.Status = ResultStatus.FAILURE;
  681. r.Messages.Add("Title contains special characters.");
  682. r.Result = false;
  683. return r;
  684. }
  685. nameInUse = (ComponentRepository.GetSavedGames().Any(f => f.Name.ToLowerInvariant().Equals(title.ToLowerInvariant())));
  686. if (nameInUse)
  687. {
  688. r.Status = ResultStatus.FAILURE;
  689. r.Messages.Add("Title currently in use.");
  690. r.Result = false;
  691. return r;
  692. }
  693. r.Messages.Add("Game title is valid");
  694. return r;
  695. }
  696. [OperationBehavior]
  697. public IServiceResult<bool> ScenarioTitleIsValid(string title)
  698. {
  699. var r = new ServiceResult<bool>
  700. {
  701. Status = ResultStatus.SUCCESS,
  702. Result = true
  703. };
  704. var validScenario = false;
  705. validScenario = (ComponentRepository.GetScenarios().Any(f => f.Name.ToLowerInvariant().Equals(title.ToLowerInvariant())));
  706. if (!validScenario)
  707. {
  708. r.Status = ResultStatus.FAILURE;
  709. r.Messages.Add("{0} is not a currently available scenario.".F(title));
  710. r.Result = false;
  711. return r;
  712. }
  713. r.Messages.Add("Scenario title is valid");
  714. return r;
  715. }
  716. // Battle
  717. [OperationBehavior]
  718. public IServiceResult<ISkirmish> CheckSkirmishVictoryCondition(ISkirmish skirmish)
  719. {
  720. var r = new ServiceResult<ISkirmish> {Status = ResultStatus.SUCCESS, Result = skirmish};
  721. var attackerDestroyed = (skirmish.Destroyed.Any() && skirmish.Destroyed.Contains(skirmish.Attacker));
  722. var defenderDestroyed = (skirmish.Destroyed.Any() && skirmish.Destroyed.Contains(skirmish.Defender));
  723. if (attackerDestroyed && defenderDestroyed)
  724. {
  725. skirmish.VictoryCondition = BattleVictoryCondition.ALL_DESTROYED;
  726. r.Messages.Add("Both units were destroyed.");
  727. return r;
  728. }
  729. if (attackerDestroyed && !defenderDestroyed)
  730. {
  731. skirmish.VictoryCondition = BattleVictoryCondition.DEFENDERS_VICTORIOUS;
  732. r.Messages.Add("{0} was destroyed.".F(skirmish.Destroyed.Single().Name));
  733. return r;
  734. }
  735. if (!attackerDestroyed && defenderDestroyed)
  736. {
  737. skirmish.VictoryCondition = BattleVictoryCondition.ATTACKERS_VICTORIOUS;
  738. r.Messages.Add("{0} was destroyed.".F(skirmish.Destroyed.Single().Name));
  739. return r;
  740. }
  741. if (skirmish.DefenderEvaded)
  742. {
  743. skirmish.VictoryCondition = BattleVictoryCondition.EVADED;
  744. r.Messages.Add("{0} evaded attack.".F(skirmish.Defender.Name));
  745. return r;
  746. }
  747. return r;
  748. }
  749. [OperationBehavior]
  750. public IServiceResult<IBattle> CheckBattleVictoryCondition(IBattle battle)
  751. {
  752. // - One or both of the battle factions have no remaining units
  753. // - No suitable defenders left for attackers
  754. // Local - surrender and retreat
  755. // Remote - stalemate
  756. // - no victor yet
  757. var r = new ServiceResult<IBattle> {Status = ResultStatus.SUCCESS, Result = battle};
  758. try
  759. {
  760. if (battle.Attackers.Any() && battle.Defenders.Any())
  761. {
  762. if (!JTSServices.RulesService.UnitsCanDoBattleWithUnits(battle.Attackers, battle.Defenders, battle.BattleType).Result)
  763. {
  764. if (battle.BattleType == BattleType.LOCAL)
  765. {
  766. // No suitable defenders to attack in a local battle.
  767. // This emulates that the roles would be reversed and the attackers may have no
  768. // Defence against the remaining defenders. Attackers will be forced to surrender
  769. // and retreat.
  770. battle.VictoryCondition = BattleVictoryCondition.SURRENDER;
  771. battle.VictorFaction = battle.DefenderFaction;
  772. return r;
  773. }
  774. if (battle.BattleType == BattleType.BARRAGE)
  775. {
  776. // In remote battle where the barrage is limited to number of available attack points
  777. // if attackers and defenders deplete these points and remain standing,
  778. // the battle is a stalemate. Victory will still be to the defender as they have held off
  779. // the barrage
  780. battle.VictoryCondition = BattleVictoryCondition.STALEMATE;
  781. battle.VictorFaction = battle.DefenderFaction;
  782. return r;
  783. }
  784. }
  785. // Battle is not over.....
  786. battle.VictoryCondition = BattleVictoryCondition.NO_VICTOR;
  787. return r;
  788. }
  789. if (!battle.Attackers.Any() && !battle.Defenders.Any())
  790. {
  791. // All units destroyed
  792. battle.VictorFaction = battle.DefenderFaction;
  793. battle.VictoryCondition = BattleVictoryCondition.ALL_DESTROYED;
  794. return r;
  795. }
  796. // Attackers win
  797. if (battle.Attackers.Any())
  798. {
  799. battle.VictoryCondition = BattleVictoryCondition.ATTACKERS_VICTORIOUS;
  800. battle.VictorFaction = battle.AttackerFaction;
  801. return r;
  802. }
  803. // Defenders win
  804. if (battle.Defenders.Any())
  805. {
  806. battle.VictoryCondition = BattleVictoryCondition.DEFENDERS_VICTORIOUS;
  807. battle.VictorFaction = battle.DefenderFaction;
  808. return r;
  809. }
  810. }
  811. catch (Exception ex)
  812. {
  813. r.Status = ResultStatus.EXCEPTION;
  814. r.ex = ex;
  815. return r;
  816. }
  817. r.Status = ResultStatus.FAILURE;
  818. r.Messages.Add("No victory condition could be met.");
  819. return r;
  820. }
  821. [OperationBehavior]
  822. public IServiceResult<bool> BattleCanContinue(IBattle battle)
  823. {
  824. // 1. Can any attacking units fire at defending units?
  825. // 2. ???
  826. var r = new ServiceResult<bool> {Status = ResultStatus.SUCCESS, Result = true};
  827. if (!battle.Attackers.Any() || !battle.Defenders.Any())
  828. {
  829. r.Status = ResultStatus.FAILURE;
  830. r.Result = false;
  831. r.Messages.Add("No Defenders or Attackers left in battle.");
  832. return r;
  833. }
  834. if (!UnitsCanDoBattleWithUnits(battle.Attackers, battle.Defenders, battle.BattleType).Result)
  835. {
  836. r.Status = ResultStatus.FAILURE;
  837. r.Result = false;
  838. r.Messages.Add("There are no attackers able to do battle with the current defenders.");
  839. return r;
  840. }
  841. r.Messages.Add("Battle can continue.");
  842. return r;
  843. }
  844. // AI
  845. [OperationBehavior]
  846. public IServiceResult<StrategicAssessmentRating> GetOverallRatingForStrategicAssessment(StrategicAssessmentInfo assessment)
  847. {
  848. var r = new ServiceResult<StrategicAssessmentRating> {Status = ResultStatus.SUCCESS};
  849. double total = 0;
  850. try
  851. {
  852. PropertyInfo[] props = typeof(StrategicAssessmentInfo).GetProperties()
  853. .Where(p => p.PropertyType == typeof(StrategicAssessmentRating)).ToArray();
  854. // We didn't pick up any of the assessment properties for some reason
  855. if (!props.Any())
  856. {
  857. r.Status = ResultStatus.FAILURE;
  858. r.Messages.Add("No property values could be ascertained for the assessment object.");
  859. return r;
  860. }
  861. Action<PropertyInfo> infoAction = p =>
  862. {
  863. lock (props)
  864. {
  865. total += Convert.ToInt32(p.GetValue(assessment, null));
  866. }
  867. };
  868. if (Convert.ToBoolean(ConfigurationManager.AppSettings["run_multithreaded"]))
  869. {
  870. Parallel.ForEach(props, infoAction);
  871. }
  872. else
  873. {
  874. props.ForEach(p => infoAction(p));
  875. }
  876. total = (total/props.Count());
  877. r.Result = (StrategicAssessmentRating) Math.Round(total);
  878. return r;
  879. }
  880. catch (Exception ex)
  881. {
  882. r.Status = ResultStatus.EXCEPTION;
  883. r.ex = ex;
  884. return r;
  885. }
  886. }
  887. #region Rules Calculations
  888. [OperationBehavior]
  889. public IServiceResult<double> CalculateTotalUnitWeight(IUnit unit)
  890. {
  891. var r = new ServiceResult<double> {Status = ResultStatus.SUCCESS};
  892. List<IUnit> allUnits;
  893. try
  894. {
  895. allUnits = unit.GetAllAttachedUnits().ToList();
  896. allUnits.Add(unit);
  897. }
  898. catch (Exception ex)
  899. {
  900. r.Status = ResultStatus.EXCEPTION;
  901. r.ex = ex;
  902. r.Messages.Add("Could not get attached units for {0}".F(unit.Name));
  903. return r;
  904. }
  905. try
  906. {
  907. r.Result = allUnits.Sum(s => CalculateUnitWeight(s).Result);
  908. return r;
  909. }
  910. catch (Exception ex)
  911. {
  912. r.Status = ResultStatus.EXCEPTION;
  913. r.ex = ex;
  914. return r;
  915. }
  916. }
  917. [OperationBehavior]
  918. public IServiceResult<double> CalculateUnitWeight(IUnit unit)
  919. {
  920. var r = new ServiceResult<double> {Status = ResultStatus.SUCCESS};
  921. var weightBase = JTSServices.DataService.GetBasePointValues().WeightBase;
  922. r.Result = (unit.UnitInfo.UnitType.UnitWeightModifier * weightBase);
  923. return r;
  924. }
  925. [OperationBehavior]
  926. public IServiceResult<double> CalculateAllowableTransportWeight(IUnit unit)
  927. {
  928. var r = new ServiceResult<double>();
  929. var weightBase = JTSServices.DataService.GetBasePointValues().WeightBase;
  930. r.Result = (unit.UnitInfo.UnitType.AllowableWeightModifier * weightBase);
  931. return r;
  932. }
  933. [OperationBehavior]
  934. public double? CalculateMovementHeuristic(IPathableObject component)
  935. {
  936. double? retVal = 0;
  937. //retVal = 10*
  938. // (Math.Abs(component.Location.X - component.Target.Location.X) +
  939. // Math.Abs(component.Location.Y - component.Target.Location.Y));
  940. retVal = Math.Max(Math.Abs(component.Location.X - component.Target.Location.X),
  941. Math.Abs(component.Location.Y - component.Target.Location.Y));
  942. // Use cross-product to favor direct paths
  943. var dx1 = component.Location.X - component.Target.Location.X;
  944. var dy1 = component.Location.Y - component.Target.Location.Y;
  945. var dx2 = component.Source.Location.X - component.Target.Location.X;
  946. var dy2 = component.Source.Location.Y - component.Target.Location.Y;
  947. var cross = Math.Abs((dx1 * dy2) - (dx2 * dy1));
  948. retVal += (cross * Math.Sqrt(2));
  949. return retVal;
  950. }
  951. [OperationBehavior]
  952. public IServiceResult<int> CalculateTotalVictoryPoints(IFaction faction)
  953. {
  954. var r = new ServiceResult<int>();
  955. // Start off with total of faction-held nodes/tiles
  956. var nodes = JTSServices.NodeService.GetAllNodes();
  957. var fromNodes = nodes.Where(n => n.Faction.Equals(faction))
  958. .Sum(n => n.DefaultTile().VictoryPoints);
  959. // One point per squad. Maybe make this dynamic? Increase with experience or some such
  960. var fromSquads = JTSServices.UnitService.GetAllUnits(new[] { faction }).Count();
  961. r.Result = (fromNodes + fromSquads);
  962. return r;
  963. }
  964. [OperationBehavior]
  965. public IServiceResult<int> CalculateReinforcementPointsForTurn(IPlayer player)
  966. {
  967. // Percentage of total board held by country
  968. // Percentage of total board held by other faction countries
  969. // Percentage of victory points held by country
  970. var r = new ServiceResult<int>();
  971. var allNodes = JTSServices.NodeService.GetAllNodes();
  972. var totalNodeCount = allNodes.Count();
  973. var playerNodeCount = allNodes.Count(n => n.Country.Equals(player.Country));
  974. var otherFactionNodeCount = allNodes.Count(n => n.Country.Faction.Equals(player.Country.Faction) &&
  975. !n.Country.Equals(player.Country));
  976. var playerVictoryPoints = allNodes
  977. .Where(n => n.Country.Equals(player.Country))
  978. .Sum(n => n.DefaultTile().VictoryPoints);
  979. var pctOfTotalNodes_Country = (Convert.ToDouble(playerNodeCount) / Convert.ToDouble(totalNodeCount));
  980. var pctOfTotalNodes_Faction = (Convert.ToDouble(otherFactionNodeCount) / Convert.ToDouble(totalNodeCount));
  981. // Calculate points based on Base Game Point Value modifiers
  982. var totalPoints = (pctOfTotalNodes_Country * BaseGamePointValues.ReinforcementCalcBaseCountry) +
  983. (pctOfTotalNodes_Faction * BaseGamePointValues.ReinforcementCalcBaseFaction) +
  984. (playerVictoryPoints * BaseGamePointValues.ReinforcementCalcBaseVP);
  985. r.Result = Convert.ToInt32(Math.Floor(totalPoints));
  986. return r;
  987. }
  988. [OperationBehavior]
  989. public double CalculateUnitAttackValueForCurrentGeog(IUnit unit)
  990. {
  991. // Combat base value
  992. // Unit Net Attack adjustment
  993. // Occupied tile attack adjustment
  994. // HQ'd bonus
  995. // Supplied bonus
  996. var retVal = unit.GetNetAttackAdjustment() +
  997. unit.GetNode().DefaultTile().GetNetAttackAdjustment() +
  998. ((unit.AttachedToUnit != null) ? BaseGamePointValues.HQBonus : 0) +
  999. ((UnitIsSupplied(unit).Result) ? BaseGamePointValues.SuppliedBonus : 0) +
  1000. BaseGamePointValues.CombatBase;
  1001. if (retVal > BaseGamePointValues.CombatRoll) retVal = BaseGamePointValues.CombatRoll;
  1002. if (retVal < 0) retVal = 0;
  1003. return retVal;
  1004. }
  1005. [OperationBehavior]
  1006. public double CalculateUnitDefenceValueForCurrentGeog(IUnit unit)
  1007. {
  1008. // Combat base value
  1009. // Unit Net Defence adjustment
  1010. // Occupied tile defence adjustment
  1011. // HQ'd bonus
  1012. // Supplied bonus
  1013. var retVal = unit.GetNetDefenceAdjustment() +
  1014. unit.GetNode().DefaultTile().GetNetDefenceAdjustment() +
  1015. ((unit.AttachedToUnit != null) ? BaseGamePointValues.HQBonus : 0) +
  1016. ((UnitIsSupplied(unit).Result) ? BaseGamePointValues.SuppliedBonus : 0) +
  1017. BaseGamePointValues.CombatBase;
  1018. if (retVal > BaseGamePointValues.CombatRoll) retVal = BaseGamePointValues.CombatRoll;
  1019. if (retVal < 0) retVal = 0;
  1020. return retVal;
  1021. }
  1022. [OperationBehavior]
  1023. public double CalculateUnitStealthValueForCurrentGeog(IUnit unit)
  1024. {
  1025. // Combat base value
  1026. // Unit Net Defence adjustment
  1027. // Occupied tile defence adjustment
  1028. var retVal = unit.GetNetStealthAdjustment() +
  1029. unit.GetNode().DefaultTile().GetNetStealthAdjustment() +
  1030. BaseGamePointValues.StealthBase;
  1031. if (retVal > BaseGamePointValues.StealthRoll) retVal = BaseGamePointValues.StealthRoll;
  1032. if (retVal < 0) retVal = 0;
  1033. return retVal;
  1034. }
  1035. [OperationBehavior]
  1036. public double CalculateUnitStrength(IUnit unit)
  1037. {
  1038. // Based on:
  1039. // unit cost factor
  1040. // unit movement
  1041. // total unit attack and defend (after adjustments)
  1042. // unit attack distance
  1043. // 1/2 unit stealth
  1044. var strength = 0.0;
  1045. strength += unit.GetNetCostMultiplier();
  1046. strength += unit.GetNetAttackAdjustment() + unit.GetNetDefenceAdjustment();
  1047. strength += unit.MovementPoints;
  1048. strength += unit.GetNetAttackDistanceAdjustment();
  1049. strength += (unit.GetFullNetStealthValue() / 2);
  1050. return strength;
  1051. }
  1052. [OperationBehavior]
  1053. public double CalculateTargetDesirabilityForUnit(IUnit unit)
  1054. {
  1055. // bonus if unit has attached units (depletes HQ bonus for attached units)
  1056. // bonus for medical or supply (depletes medical bonus roll and supply bonuses for other units)
  1057. // Concept is that since Medical and supply give combat bonuses to other units, they have a priority
  1058. // HQ units (units with units attached) give combat bonuses to others, they have a priority
  1059. // but based on the number of units affected
  1060. // The rest are based on a relative 'strength' metric
  1061. var attachedUnits = unit.GetAllAttachedUnits();
  1062. // Baseline
  1063. var factor = unit.GetNetStrengthFactor();
  1064. // Bonuses
  1065. factor += (attachedUnits.Any() ? (attachedUnits.Count() * BaseGamePointValues.TargetAttachedUnitBonus) : 0);
  1066. factor += (unit.IsUnitClass("medical")) ? BaseGamePointValues.TargetMedicalUnitBonus : 0;
  1067. factor += (unit.IsUnitClass("supply")) ? BaseGamePointValues.TargetSupplyUnitBonus : 0;
  1068. return factor;
  1069. }
  1070. [OperationBehavior]
  1071. public IServiceResult<double> CalculateTotalRPCostForUnit(IUnit unit)
  1072. {
  1073. var r = new ServiceResult<double> { Status = ResultStatus.SUCCESS };
  1074. try
  1075. {
  1076. r.Result = CalculateTotalRPByUnitTypeUnitClass(unit.UnitInfo.UnitType, unit.UnitInfo.UnitClass);
  1077. }
  1078. catch (Exception ex)
  1079. {
  1080. r.Status = ResultStatus.EXCEPTION;
  1081. r.ex = ex;
  1082. }
  1083. return r;
  1084. }
  1085. [OperationBehavior]
  1086. public double CalculateTotalRPByUnitTypeUnitClass(IUnitType ut, IUnitClass uc)
  1087. {
  1088. return Math.Ceiling(((ut.UnitCostModifier + uc.UnitCostModifier) * BaseGamePointValues.CostBase));
  1089. }
  1090. [OperationBehavior]
  1091. public int CalculateThreatDistance(int factorValue, int baseValue)
  1092. {
  1093. return Convert.ToInt32(Math.Round((Convert.ToDouble(factorValue) - 1) * 10 / baseValue));
  1094. }
  1095. [OperationBehavior]
  1096. public IServiceResult<double> CalculateOffensiveStrategicImportance(INode node)
  1097. {
  1098. var r = CalculateGeneralStrategicImportance(node);
  1099. if (r.Status != ResultStatus.SUCCESS) { return r; }
  1100. r.Result += node.DefaultTile().GetNetAttackAdjustment();
  1101. return r;
  1102. }
  1103. [OperationBehavior]
  1104. public IServiceResult<double> CalculateDefensiveStrategicImportance(INode node)
  1105. {
  1106. var r = CalculateGeneralStrategicImportance(node);
  1107. if (r.Status != ResultStatus.SUCCESS) { return r; }
  1108. r.Result += node.DefaultTile().GetNetDefenceAdjustment();
  1109. return r;
  1110. }
  1111. [OperationBehavior]
  1112. private IServiceResult<double> CalculateGeneralStrategicImportance(INode node)
  1113. {
  1114. var r = new ServiceResult<double> { Status = ResultStatus.SUCCESS };
  1115. var demographicModifier = node.DefaultTile().GetNetStealthAdjustment();
  1116. return r;
  1117. }
  1118. #endregion
  1119. #endregion
  1120. // Utility
  1121. [OperationBehavior]
  1122. public bool DemographicIsHybrid(IDemographic demographic)
  1123. {
  1124. return DataRepository.GetHybridDemographicsClasses().Contains(demographic.DemographicClass.ID);
  1125. }
  1126. }
  1127. }