internal override void AddEntry(MoveLine moveLine) { if (moveLine.Amount < 0) { Debit += -moveLine.Amount; } else { Credit += moveLine.Amount; } }
/// <summary> /// removes units from all other paths that, if seen, could cause specified units to be removed from specified segments; /// returns whether successful /// </summary> public bool deleteOtherPaths(IEnumerable<SegmentUnit> segmentUnits, bool addDeleteLines, bool addKeepLines) { HashSet<SegmentUnit> ancestors = new HashSet<SegmentUnit>(); HashSet<SegmentUnit> prev = new HashSet<SegmentUnit>(); HashSet<SegmentUnit> liveToNonLivePrev = new HashSet<SegmentUnit>(); // live prev segments whose next ancestor is not live bool success = true; bool deleted = false; foreach (SegmentUnit segmentUnit in segmentUnits) { addAncestors (segmentUnit, ancestors, prev, liveToNonLivePrev); } foreach (SegmentUnit ancestor in prev) { foreach (SegmentUnit segmentUnit in ancestor.next ()) { if (!ancestors.Contains (segmentUnit)) { success &= segmentUnit.delete (addDeleteLines); deleted = true; } } } foreach (SegmentUnit ancestor in liveToNonLivePrev) { foreach (SegmentUnit segmentUnit in ancestor.next ()) { if (segmentUnit.segment.path.timeSimPast != long.MaxValue && !ancestors.Contains (segmentUnit)) { success &= segmentUnit.delete (addDeleteLines); deleted = true; } } } if (addKeepLines && deleted) { // add kept unit lines // TODO: tweak time if deleted in past MoveLine keepLine = new MoveLine(timeSim, segmentUnits.First ().unit.player); foreach (SegmentUnit ancestor in ancestors) { if (segmentUnits.Where (u => u.unit == ancestor.unit).Any ()) { keepLine.vertices.AddRange (ancestor.segment.path.moveLines (ancestor.segment.timeStart, (ancestor.segment.nextOnPath () == null) ? keepLine.time : ancestor.segment.nextOnPath().timeStart)); } } keepLines.Add (keepLine); } return success; }
/// <returns>path that was moved (might not be original path)</returns> public Path moveTo(long time, List<Unit> units, FP.Vector pos, bool autoTimeTravel) { Path movedPath; FP.Vector goalPos = pos; // don't move off map edge if (goalPos.x < 0) goalPos.x = 0; if (goalPos.x > g.mapSize) goalPos.x = g.mapSize; if (goalPos.y < 0) goalPos.y = 0; if (goalPos.y > g.mapSize) goalPos.y = g.mapSize; if (autoTimeTravel && units.Find (u => { Waypoint waypoint = g.tileAt (goalPos).waypointWhen (u, time); return !Waypoint.active (waypoint) || Move.fromSpeed (waypoint.time, speed, waypoint.tile.centerPos (), goalPos).timeEnd > time; }) == null) { // move units with automatic time travel List<Path> movedPaths = new List<Path>(); long stackTime = long.MinValue; foreach (Unit unit in units) { Waypoint waypoint = g.tileAt (goalPos).waypointWhen (unit, time); // make moves list using waypoints List<Move> waypointMoves = new List<Move> { Move.fromSpeed (waypoint.time, speed, waypoint.tile.centerPos (), goalPos) }; while (waypoint.prev != null) { waypointMoves.Insert (0, new Move(waypoint.time - (waypointMoves[0].vecStart - waypoint.prev.tile.centerPos()).length () / speed, waypoint.time, waypoint.prev.tile.centerPos (), waypointMoves[0].vecStart)); waypoint = waypoint.prev; } waypointMoves.Insert (0, new Move(waypoint.start[0].time, waypoint.time, waypoint.start[0].path.posWhen (waypoint.start[0].time), waypointMoves[0].vecStart)); // do path smoothing for (int i = 0; i < waypointMoves.Count; i++) { int j; for (j = i + 1; j < waypointMoves.Count; j++) { Move move = Move.fromSpeed(waypointMoves[i].timeStart, speed, waypointMoves[i].vecStart, waypointMoves[j].vecEnd); long timeMove = (waypointMoves[i].timeStart / g.tileInterval + 1) * g.tileInterval; while (timeMove < waypointMoves[j].timeEnd && g.tileAt(move.posWhen(timeMove)).exclusiveWhen(player, timeMove)) { timeMove += g.tileInterval; } if (timeMove < waypointMoves[j].timeEnd) break; } j--; if (j > i) { waypointMoves[i] = Move.fromSpeed(waypointMoves[i].timeStart, speed, waypointMoves[i].vecStart, waypointMoves[j].vecEnd); waypointMoves.RemoveRange(i + 1, j - i); } } // if unit not found on start waypoint, add it back to past segments for (int i = 0; i < waypoint.start.Count - 1; i++) { Segment segment = waypoint.start[i + 1].path.insertSegment (waypoint.start[i].time); while (segment.timeStart != waypoint.start[i + 1].time) { segment = segment.prevOnPath (); if (segment.units.Contains (unit)) { i = waypoint.start.Count; break; } segment.units.Add (unit); segment.deletedUnits.Remove(unit); } } // make non-live path moving along waypoints if (!waypoint.start[0].segment().path.makePath (waypointMoves[0].timeStart, new List<Unit> { unit })) { throw new SystemException("make auto time travel path failed when moving units"); } g.paths.Last ().moves = waypointMoves; g.paths.Last ().updatePast(time, false); // add kept unit line MoveLine keepLine = new MoveLine(time, player); keepLine.vertices.AddRange (g.paths.Last ().moveLines (waypointMoves[0].timeStart, time)); g.keepLines.Add (keepLine); g.alternatePaths.Add (g.paths.Last ()); movedPaths.Add (g.paths.Last ()); stackTime = Math.Max (stackTime, waypointMoves.Last ().timeEnd); } player.updatePast (time); if (units.Count > 1) new StackEvt(stackTime, movedPaths, nSeeUnits).apply (g); movedPath = movedPaths.Find (p => p.segments.Last ().units.Count > 0); } else { // move units normally (without automatic time travel) movedPath = this; // move this path by default if (time < g.timeSim) { // move non-live path if in past // if this path already isn't live, a better approach might be removing later segments and moves then moving this path, like pre-stacking versions (see ISSUE #27) if (!makePath (time, units)) throw new SystemException("make non-live path failed when moving units"); movedPath = g.paths.Last (); } else { foreach (Unit unit in segmentWhen(time).units) { if (!units.Contains (unit)) { // some units in path aren't being moved, so make a new path if (!makePath (time, units, true)) throw new SystemException("make new path failed when moving units"); movedPath = g.paths.Last (); break; } } } movedPath.moveToDirect (time, pos); if (movedPath != this) g.alternatePaths.Add (movedPath); } if (movedPath != this && (movedPath.timeSimPast == long.MaxValue || timeSimPast != long.MaxValue)) { // new path was moved, so try to remove units that moved from current path Segment segment = segmentWhen (time); foreach (Unit unit in units) { new SegmentUnit(segment, unit).delete (); } } return movedPath; }
/// <summary> /// removes unit from this segment and fewest possible unseen segments such that all remaining possibilities are valid, /// returns whether successful /// </summary> public bool delete(bool addMoveLines = false) { // ***************** // BEGIN DANGER ZONE // ***************** // There are many non-trivial cases this function needs to handle, so only modify it if you know what you are doing. // Even then, you will likely break a corner case anyway but not notice until a week or two later. // // If what you want to do is actually some sort of postprocessing using the deleted SegmentUnits, // it's best to do it at the end of the function (after the "danger zone") by iterating over the "removed" variable. if (!segment.units.Contains (unit)) return true; // if this segment already doesn't contain this unit, return true List<SegmentUnit> ancestors = new List<SegmentUnit> { this }; Dictionary<Segment, List<Unit>> removed = new Dictionary<Segment, List<Unit>>(); long timeEarliestChild = long.MaxValue; int i; // find all ancestor segments to start removal from for (i = 0; i < ancestors.Count; i++) { if (ancestors[i].prev ().Any ()) { // if this ancestor has a sibling segment that we're not currently planning to remove unit from, // don't remove unit from previous segments shared by both Unit u = unit; if (!ancestors[i].segment.branches.Where(seg => seg.units.Contains(u) && !ancestors.Contains(new SegmentUnit(seg, u)) && (seg.path.timeSimPast == long.MaxValue || ancestors[i].segment.path.timeSimPast != long.MaxValue)).Any()) { // indicate to remove unit from previous segments ancestors.AddRange (ancestors[i].prev ()); ancestors.RemoveAt(i); i--; } } else if (ancestors[i].segment.prev ().Any ()) { // unit has a parent but we're deleting its first segment, so may need to check resources starting at this time if (ancestors[i].unit.attacks.Count > 0) return false; if (ancestors[i].segment.timeStart < timeEarliestChild && ancestors[i].unit.type.rscCollectRate.Where (r => r > 0).Any ()) { timeEarliestChild = ancestors[i].segment.timeStart; } } else { // reached a segment with no previous segment whatsoever, so return false (we assume other players know the scenario's starting state) return false; } } // remove unit recursively, starting at the ancestor segments we found for (i = 0; i < ancestors.Count; i++) { if (!ancestors[i].deleteAfter (ref removed, ref timeEarliestChild)) break; } // if a deleteAfter() call failed or removing unit led to player ever having negative resources, // add units back to segments they were removed from if (i < ancestors.Count || (timeEarliestChild != long.MaxValue && segment.path.player.checkNegRsc (timeEarliestChild, false) >= 0)) { foreach (KeyValuePair<Segment, List<Unit>> item in removed) { item.Key.units.AddRange (item.Value); } return false; } // *************** // END DANGER ZONE // *************** foreach (KeyValuePair<Segment, List<Unit>> item in removed) { // add deleted units to list if (item.Key.timeStart < g.timeSim) { if (item.Key.nextOnPath () == null) item.Key.path.insertSegment (g.timeSim); item.Key.deletedUnits.AddRange (item.Value); } } if (addMoveLines) { // add deleted unit lines // TODO: tweak time if deleted before timeSimPast MoveLine deleteLine = new MoveLine(Math.Min (Math.Max (segment.path.timeSimPast, segment.path.moves[0].timeStart), g.timeSim), unit.player); foreach (Segment seg in removed.Keys) { deleteLine.vertices.AddRange (seg.path.moveLines (seg.timeStart, (seg.nextOnPath () == null || seg.nextOnPath ().timeStart > deleteLine.time) ? deleteLine.time : seg.nextOnPath ().timeStart)); } g.deleteLines.Add (deleteLine); } return true; }