/// <summary> /// Handle scanning/shooting on this node: /// - Broadcasts the Struck response for the local node /// - Moves ships to the graveyard as needed /// </summary> private Messages.Struck HandleLocalScanShoot(Messages.ScanShoot msg) { Messages.Struck results = QuadTreeNode.ScanShootLocal(msg); Bus.BroadcastMessage(results); foreach (var ourStruck in results.ShipsInfo) { if (ourStruck.Damage < 0.0) // ship ded { var ourDeadShip = ourStruck.Ship; QuadTreeNode.ShipsByToken.Remove(ourDeadShip.Token); DeadShips.Add(ourDeadShip.Token, ourDeadShip); } } return(results); }
public override Messages.Struck ScanShootLocal(Messages.ScanShoot msg) { bool affected = MathUtils.DoesQuadIntersectCircleSector(this.Bounds, msg); if (affected) { Bus.SendMessage(msg, NodePeer); var struckTask = new MessageWaiter <Messages.Struck>(Bus, NodePeer, (struck) => struck.Originator == msg.Originator).Wait; Messages.Struck results = new Messages.Struck(); if (Task.WaitAll(new Task[] { struckTask }, REPLYTIMEOUT)) { results.OriginatorAreaGain += struckTask.Result.OriginatorAreaGain; results.ShipsInfo.AddRange(struckTask.Result.ShipsInfo); } return(results); } else { return(null); } }
public override Messages.Struck ScanShootLocal(Messages.ScanShoot msg) { Messages.Struck results = new Messages.Struck(); // 1) Search ships locally (but only if affected by the scan) //bool affected = MathUtils.DoesQuadIntersectCircleSector(this.Bounds, msg); bool affected = true; // FIXME - This is here to make sure scans always go through; ideally, though, ships would always be in the SGame node that manages them... if (affected) { Vector2 leftPoint = MathUtils.DirVec(msg.Direction + msg.Width) * msg.Radius; Vector2 rightPoint = MathUtils.DirVec(msg.Direction - msg.Width) * msg.Radius; Console.WriteLine($"Scanning with radius {msg.Radius}, in triangle <{msg.Origin}, {leftPoint}, {rightPoint}>"); var iscanned = ShipsByToken.Values .Where((ship) => ship.Token != msg.Originator && (MathUtils.CircleTriangleIntersection(ship.Pos, ship.Radius(), msg.Origin, leftPoint, rightPoint) || MathUtils.CircleSegmentIntersection(ship.Pos, ship.Radius(), msg.Origin, msg.Radius, msg.Direction, msg.Width)) ) .Select((ship) => new Messages.Struck.ShipInfo() { Ship = ship }); results.ShipsInfo.AddRange(iscanned); if (msg.ScaledShotEnergy > 0.0) { foreach (var struck in results.ShipsInfo) { var ourShip = (LocalSpaceship)struck.Ship; double shipDistance = (ourShip.Pos - msg.Origin).Length(); double damage = MathUtils.ShotDamage(msg.ScaledShotEnergy, msg.Width, shipDistance); double shielding = MathUtils.ShieldingAmount(ourShip, msg.Origin, msg.Direction, msg.Width, msg.Radius); if (shielding > 0.0) { Console.WriteLine($"{ourShip.PublicId} shielded itself for {shielding * 100.0}% of {msg.Originator}'s shot (= {damage * shielding} damage)"); } damage *= (1.0 - shielding); // We have killed a ship, gain it's kill reward, and move struck ship to the graveyard if (ourShip.Area - damage < MINIMUM_AREA) { results.OriginatorAreaGain += ourShip.KillReward; struck.Damage = -damage; } else // Struck ship survived - note that it's in combat { if (ourShip.LastUpdate - ourShip.LastCombat > LocalSpaceship.COMBAT_COOLDOWN) { // Reset kill reward when hit ship was not in combat ourShip.KillReward = ourShip.Area; } ourShip.LastCombat = ourShip.LastUpdate; ourShip.Area -= damage; struck.Damage = damage; } } } } return(results); }
public async Task Shoot(ApiResponse response, ApiData data) { #if DEBUG // HACK: Force game update so older tests still function UpdateGameState(); #endif LocalSpaceship ship = await IntersectionParamCheck(response, data, true); if (ship == null) { return; } double widthDeg = (double)data.Json["width"]; double directionDeg = (double)data.Json["direction"]; double damageScaling = (double)data.Json["damage"]; int energy = (int)Math.Min((int)data.Json["energy"], Math.Floor(ship.Energy / damageScaling)); ship.Energy -= energy * damageScaling; //remove energy for the shot Console.WriteLine($"Shot by {ship.PublicId}, pos={ship.Pos}, dir={directionDeg}°, width={widthDeg}°, energy spent={energy}, scaling={damageScaling}"); // 1) Broadcast the "need to shoot this" message var shootMsg = new SShared.Messages.ScanShoot() { Originator = ship.Token, Origin = ship.Pos, Direction = MathUtils.Deg2Rad(directionDeg), ScaledShotEnergy = energy * damageScaling, Width = MathUtils.Deg2Rad(widthDeg), Radius = MathUtils.ScanShootRadius(MathUtils.Deg2Rad(widthDeg), energy), }; // Construct waiters BEFORE we potentially get a reply so that we know for sure it will reach us var resultWaiters = Bus.Host.ConnectedPeerList .Where(peer => peer != ArbiterPeer) .Select(peer => new MessageWaiter <Messages.Struck>(Bus, peer, struck => struck.Originator == shootMsg.Originator).Wait) .ToArray(); Bus.BroadcastMessage(shootMsg, excludedPeer: ArbiterPeer); // 2) Shoot locally and broadcast the results of the local shoot Messages.Struck results = HandleLocalScanShoot(shootMsg); // 3) Wait for the scanning results of all other nodes Task.WaitAll(resultWaiters, ScanShootTimeout); // 4) Combine the results that arrived with our local ones to find the complete list of all victims foreach (var waiter in resultWaiters) { if (waiter.Status != TaskStatus.RanToCompletion) { continue; } lock (results) { results.ShipsInfo.AddRange(waiter.Result.ShipsInfo); results.OriginatorAreaGain += waiter.Result.OriginatorAreaGain; } } // 5) Apply area gain to the local shooter ship (if any) ship.Area += results.OriginatorAreaGain; JArray respDict = new JArray(); foreach (var struckShip in results.ShipsInfo) { // ignore our ship if (struckShip.Ship.Token == ship.Token) { continue; } double preShotArea = struckShip.Ship.Area + Math.Abs(struckShip.Damage); //The api doesnt have a return value for shooting, but ive left this in for now for testing purposes. JToken struckShipInfo = new JObject(); struckShipInfo["id"] = struckShip.Ship.PublicId; struckShipInfo["area"] = preShotArea; struckShipInfo["posX"] = struckShip.Ship.Pos.X; struckShipInfo["posY"] = struckShip.Ship.Pos.Y; respDict.Add(struckShipInfo); } //Ship performed combat action, lock kill reward if not in combat from before if (ship.LastUpdate - ship.LastCombat > LocalSpaceship.COMBAT_COOLDOWN) { ship.KillReward = ship.Area; } ship.LastCombat = ship.LastUpdate; response.Data["struck"] = respDict; await response.Send(); }
public async Task Scan(ApiResponse response, ApiData data) { var ship = await IntersectionParamCheck(response, data); if (ship == null) { return; } int energy = (int)data.Json["energy"]; double directionDeg = (double)data.Json["direction"]; double widthDeg = (double)data.Json["width"]; energy = (int)Math.Min(energy, Math.Floor(ship.Energy)); ship.Energy -= energy; Console.WriteLine($"Scan by {ship.PublicId}, pos={ship.Pos}, dir={directionDeg}°, width={widthDeg}°, energy spent={energy}"); // 1) Broadcast the "need to scan this" message var scanMsg = new SShared.Messages.ScanShoot() { Originator = ship.Token, Origin = ship.Pos, Direction = MathUtils.Deg2Rad(directionDeg), ScaledShotEnergy = 0, Width = MathUtils.Deg2Rad(widthDeg), Radius = MathUtils.ScanShootRadius(MathUtils.Deg2Rad(widthDeg), energy), }; // Construct waiters BEFORE we potentially get a reply so that we know for sure it will reach us var resultWaiters = Bus.Host.ConnectedPeerList .Where(peer => peer != ArbiterPeer) .Select(peer => new MessageWaiter <Messages.Struck>(Bus, peer, struck => struck.Originator == scanMsg.Originator).Wait) .ToArray(); Bus.BroadcastMessage(scanMsg, excludedPeer: ArbiterPeer); // 2) Scan locally and broadcast the results of the local scan Messages.Struck results = HandleLocalScanShoot(scanMsg); // 3) Wait for the scanning results of all other nodes Task.WaitAll(resultWaiters, ScanShootTimeout); // 4) Combine the results that arrived with our local ones to find the whole list of scanned ships foreach (var waiter in resultWaiters) { if (waiter.Status != TaskStatus.RanToCompletion) { continue; } results.ShipsInfo.AddRange(waiter.Result.ShipsInfo); } JArray respDict = new JArray(); foreach (var scanned in results.ShipsInfo) { if (scanned.Ship.Token == ship.Token) { continue; } //The api doesnt have a return value for shooting, but ive left this in for now for testing purposes. JToken struckShipInfo = new JObject(); struckShipInfo["id"] = scanned.Ship.PublicId; struckShipInfo["area"] = scanned.Ship.Area; struckShipInfo["posX"] = scanned.Ship.Pos.X; struckShipInfo["posY"] = scanned.Ship.Pos.Y; respDict.Add(struckShipInfo); } response.Data["scanned"] = respDict; await response.Send(); }