Rewrite gun to be more modular

This commit is contained in:
UnknownShadow200 2019-01-13 14:04:12 +11:00
parent 4ed9ded251
commit 05a3de6fbe
16 changed files with 433 additions and 120 deletions

View File

@ -15,97 +15,41 @@
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using MCGalaxy.Maths;
using MCGalaxy.Tasks;
using BlockID = System.UInt16;
using System;
using MCGalaxy.Games;
namespace MCGalaxy.Commands.Fun {
public sealed class CmdGun : WeaponCmd {
public sealed class CmdGun : Command2 {
public override string name { get { return "Gun"; } }
protected override string Weapon { get { return "Gun"; } }
public override string type { get { return CommandTypes.Other; } }
public override bool museumUsable { get { return false; } }
public override LevelPermission defaultRank { get { return LevelPermission.AdvBuilder; } }
public override bool SuperUseable { get { return false; } }
protected override void OnActivated(Player p, byte yaw, byte pitch, BlockID block) {
WeaponArgs args = new WeaponArgs();
args.player = p;
args.block = block;
args.weaponType = (WeaponType)p.blockchangeObject;
args.start = MakePos(p);
args.dir = DirUtils.GetDirVector(yaw, pitch);
args.pos = args.PosAt(3);
args.iterations = 4;
SchedulerTask task = new SchedulerTask(GunCallback, args, TimeSpan.Zero, true);
p.CriticalTasks.Add(task);
}
static void GunCallback(SchedulerTask task) {
WeaponArgs args = (WeaponArgs)task.State;
if (args.moving) {
args.moving = MoveGun(args);
// Laser gun persists for a short while
if (!args.moving && args.weaponType == WeaponType.Laser)
task.Delay = TimeSpan.FromMilliseconds(400);
public override void Use(Player p, string message, CommandData data) {
if (!p.level.Config.Guns) {
p.Message("Guns cannot be used on this map!"); return;
}
if (p.weapon != null && message.Length == 0) {
p.weapon.Disable();
p.weapon = null;
return;
}
args.TeleportSourcePlayer();
if (args.weaponType == WeaponType.Laser) {
foreach (Vec3U16 pos in args.previous) {
args.player.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
args.previous.Clear();
} else if (args.previous.Count > 0) {
Vec3U16 pos = args.previous[0];
args.previous.RemoveAt(0);
args.player.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
task.Repeating = args.previous.Count > 0;
}
static bool MoveGun(WeaponArgs args) {
while (true) {
args.pos = args.PosAt(args.iterations);
args.iterations++;
Vec3U16 pos = args.pos;
BlockID cur = args.player.level.GetBlock(pos.X, pos.Y, pos.Z);
if (cur == Block.Invalid) return false;
if (cur != Block.Air && !args.allBlocks.Contains(pos) && HandlesHitBlock(args.player, cur, args.weaponType, pos, true))
return false;
args.player.level.Blockchange(pos.X, pos.Y, pos.Z, args.block);
args.previous.Add(pos);
args.allBlocks.Add(pos);
if (HitsPlayer(args, pos)) return false;
if (args.iterations > 12 && args.weaponType != WeaponType.Laser) {
pos = args.previous[0];
args.previous.RemoveAt(0);
args.player.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
if (args.weaponType != WeaponType.Laser) return true;
}
}
static Vec3U16 MakePos(Player p) { return (Vec3U16)p.Pos.BlockCoords; }
static bool HitsPlayer(WeaponArgs args, Vec3U16 pos) {
Player pl = GetPlayer(args.player, pos, true);
if (pl == null) return false;
Gun gun = GetGun(p, message);
if (gun == null) { Help(p); return; }
Player p = args.player;
if (p.level.physics >= 3 && args.weaponType >= WeaponType.Explode) {
pl.HandleDeath(Block.Cobblestone, "@p %Swas blown up by " + p.ColoredName, true);
} else {
pl.HandleDeath(Block.Cobblestone, "@p %Swas shot by " + p.ColoredName);
}
return true;
p.weapon = gun;
gun.Enable(p);
}
static Gun GetGun(Player p, string mode) {
if (mode.Length == 0) return new Gun();
if (mode.CaselessEq("destroy")) return new PenetrativeGun();
if (mode.CaselessEq("tp") || mode.CaselessEq("teleport")) return new TeleportGun();
if (mode.CaselessEq("explode")) return new ExplosiveGun();
if (mode.CaselessEq("laser")) return new LaserGun();
return null;
}
public override void Help(Player p) {

View File

@ -87,7 +87,6 @@ namespace MCGalaxy.Commands.Fun {
class AimState {
public Player player;
public Position oldPos = default(Position);
public List<Vec3U16> lastGlass = new List<Vec3U16>();
public List<Vec3U16> glassCoords = new List<Vec3U16>();
}

View File

@ -83,7 +83,7 @@ namespace MCGalaxy.Commands.Moderation {
int index = p.level.PosToInt(P.X, P.Y, P.Z);
buffer.Add(index, P.Block);
});
buffer.Send(true);
buffer.Flush();
if (op.found) {
p.Message("Now highlighting past &b{0} %Sfor {1}",

View File

@ -77,7 +77,7 @@ namespace MCGalaxy.Commands.World {
}
index++;
}
buffer.Send(true);
buffer.Flush();
}
static void FixLight(Player p, Level lvl, ref int totalFixed) {
@ -117,7 +117,7 @@ namespace MCGalaxy.Commands.World {
}
index++;
}
buffer.Send(true);
buffer.Flush();
}
public override void Help(Player p) {

View File

@ -31,7 +31,6 @@ namespace MCGalaxy.Commands.Building {
p.staticCommands = false;
p.deleteMode = false;
p.ModeBlock = Block.Invalid;
p.aiming = false;
p.onTrain = false;
p.isFlying = false;
p.BrushName = "normal";
@ -39,6 +38,7 @@ namespace MCGalaxy.Commands.Building {
p.Transform = NoTransform.Instance;
p.level.blockqueue.RemoveAll(p);
if (p.weapon != null) p.weapon.Disable();
p.Message("Every toggle or action was aborted.");
}

View File

@ -41,7 +41,7 @@ namespace MCGalaxy.Games {
if (!Running) return;
SetBoardOpening(Block.Air);
bulk.Send(true);
bulk.Flush();
if (!Running) return;
BeginRound();
@ -112,7 +112,7 @@ namespace MCGalaxy.Games {
Cuboid(4, 4, maxZ - 4, maxX - 4, 4, maxZ - 4, Block.Air);
Cuboid(4, 4, 4, 4, 4, maxZ - 4, Block.Air);
Cuboid(maxX - 4, 4, 4, maxX - 4, 4, maxZ - 4, Block.Air);
bulk.Send(true);
bulk.Flush();
}
void RemoveAllSquareBorders() {
@ -123,7 +123,7 @@ namespace MCGalaxy.Games {
for (int zz = 6 - 1; zz <= Map.Length - 6; zz += 3) {
Cuboid(4, 4, zz, maxX - 4, 4, zz, Block.Air);
}
bulk.Send(true);
bulk.Flush();
}
void RemoveSquares() {
@ -142,19 +142,19 @@ namespace MCGalaxy.Games {
void RemoveSquare(SquarePos pos) {
ushort x1 = pos.X, x2 = (ushort)(pos.X + 1), z1 = pos.Z, z2 = (ushort)(pos.Z + 1);
Cuboid(x1, 4, z1, x2, 4, z2, Block.Yellow);
bulk.Send(true);
bulk.Flush();
Thread.Sleep(Interval);
Cuboid(x1, 4, z1, x2, 4, z2, Block.Orange);
bulk.Send(true);
bulk.Flush();
Thread.Sleep(Interval);
Cuboid(x1, 4, z1, x2, 4, z2, Block.Red);
bulk.Send(true);
bulk.Flush();
Thread.Sleep(Interval);
Cuboid(x1, 4, z1, x2, 4, z2, Block.Air);
bulk.Send(true);
bulk.Flush();
// Remove glass borders, if neighbouring squares were previously removed
bool airMaxX = false, airMinZ = false, airMaxZ = false, airMinX = false;

View File

@ -128,13 +128,13 @@ namespace MCGalaxy.Games {
squaresLeft.Add(new SquarePos(xx, zz));
}
bulk.Send(true);
bulk.Flush();
}
void SetBoardOpening(BlockID block) {
int midX = Map.Width / 2, midY = Map.Height / 2, midZ = Map.Length / 2;
Cuboid(midX - 1, midY, midZ - 1, midX, midY, midZ, block);
bulk.Send(true);
bulk.Flush();
}
void Cuboid(int x1, int y1, int z1, int x2, int y2, int z2, BlockID block) {

View File

@ -0,0 +1,177 @@
/*
Copyright 2011 MCForge
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using MCGalaxy.Maths;
using MCGalaxy.Tasks;
using BlockID = System.UInt16;
namespace MCGalaxy.Games {
/// <summary> Represents a gun weapon which dies when it hits a block or a player. </summary>
/// <remarks> Fires in a straight line from where playing is looking. </remarks>
public class Gun : Weapon {
public override string Name { get { return "Gun"; } }
protected override void OnActivated(byte yaw, byte pitch, BlockID block) {
AmmunitionData args = new AmmunitionData();
args.block = block;
args.start = (Vec3U16)p.Pos.BlockCoords;
args.dir = DirUtils.GetDirVector(yaw, pitch);
args.pos = args.PosAt(3);
args.iterations = 4;
SchedulerTask task = new SchedulerTask(GunCallback, args, TimeSpan.Zero, true);
p.CriticalTasks.Add(task);
}
protected virtual bool OnHitBlock(AmmunitionData args, Vec3U16 pos, BlockID block) {
return true;
}
protected virtual void OnHitPlayer(AmmunitionData args, Player pl) {
pl.HandleDeath(Block.Cobblestone, "@p %Swas shot by " + p.ColoredName);
}
protected virtual bool TickMove(AmmunitionData args) {
if (args.iterations > 12) {
Vec3U16 pos = args.visible[0];
args.visible.RemoveAt(0);
p.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
return true;
}
protected virtual bool TickRevert(SchedulerTask task) {
AmmunitionData args = (AmmunitionData)task.State;
if (args.visible.Count > 0) {
Vec3U16 pos = args.visible[0];
args.visible.RemoveAt(0);
p.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
return args.visible.Count > 0;
}
void GunCallback(SchedulerTask task) {
AmmunitionData args = (AmmunitionData)task.State;
if (args.moving) {
args.moving = TickGun(args);
} else {
task.Repeating = TickRevert(task);
}
}
bool TickGun(AmmunitionData args) {
while (true) {
args.pos = args.PosAt(args.iterations);
args.iterations++;
Vec3U16 pos = args.pos;
BlockID cur = p.level.GetBlock(pos.X, pos.Y, pos.Z);
if (cur == Block.Invalid) return false;
if (cur != Block.Air && !args.all.Contains(pos) && OnHitBlock(args, pos, cur))
return false;
p.level.Blockchange(pos.X, pos.Y, pos.Z, args.block);
args.visible.Add(pos);
args.all.Add(pos);
Player pl = PlayerAt(p, pos, true);
if (pl != null) { OnHitPlayer(args, pl); return false; }
if (TickMove(args)) return true;
}
}
}
public class PenetrativeGun : Gun {
public override string Name { get { return "Penetrative gun"; } }
protected override bool OnHitBlock(AmmunitionData args, Vec3U16 pos, BlockID block) {
if (p.level.physics < 2 || block == Block.Glass) return true;
// Penetrative gun goes through blocks lava can go through
return !p.level.Props[block].LavaKills;
}
}
public class ExplosiveGun : Gun {
public override string Name { get { return "Explosive gun"; } }
protected override bool OnHitBlock(AmmunitionData args, Vec3U16 pos, BlockID block) {
if (p.level.physics >= 3 && block != Block.Glass) {
p.level.MakeExplosion(pos.X, pos.Y, pos.Z, 1);
}
return true;
}
protected override void OnHitPlayer(AmmunitionData args, Player pl) {
if (pl.level.physics >= 3) {
pl.HandleDeath(Block.Cobblestone, "@p %Swas blown up by " + p.ColoredName, true);
} else {
base.OnHitPlayer(args, pl);
}
}
}
public class LaserGun : ExplosiveGun {
public override string Name { get { return "Laser"; } }
protected override bool TickMove(AmmunitionData args) {
// laser immediately strikes target
return false;
}
protected override bool TickRevert(SchedulerTask task) {
AmmunitionData args = (AmmunitionData)task.State;
if (args.all.Count > 0) {
// laser persists for a short while
task.Delay = TimeSpan.FromMilliseconds(400);
args.all.Clear();
} else {
foreach (Vec3U16 pos in args.visible) {
p.level.Blockchange(pos.X, pos.Y, pos.Z, Block.Air, true);
}
args.visible.Clear();
}
return args.visible.Count > 0;
}
}
public class TeleportGun : Gun {
public override string Name { get { return "Teleporter gun"; } }
void DoTeleport(AmmunitionData args) {
int i = args.visible.Count - 3;
if (i >= 0 && i < args.visible.Count) {
Vec3U16 coords = args.visible[i];
Position pos = new Position(coords.X * 32, coords.Y * 32 + 32, coords.Z * 32);
p.SendPos(Entities.SelfID, pos, p.Rot);
}
}
protected override void OnHitPlayer(AmmunitionData args, Player pl) {
DoTeleport(args);
}
protected override bool OnHitBlock(AmmunitionData args, Vec3U16 pos, BlockID block) {
DoTeleport(args);
return true;
}
}
}

View File

@ -0,0 +1,195 @@
/*
Copyright 2011 MCForge
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
http://www.opensource.org/licenses/ecl2.php
http://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.Collections.Generic;
using MCGalaxy.Commands;
using MCGalaxy.Events.PlayerEvents;
using MCGalaxy.Maths;
using MCGalaxy.Tasks;
using BlockID = System.UInt16;
namespace MCGalaxy.Games {
/// <summary> Represents a weapon which can interact with blocks or players until it dies. </summary>
/// <remarks> Activated by clicking through either PlayerClick or a glass box around the player. </remarks>
public abstract class Weapon {
public abstract string Name { get; }
static bool hookedEvents;
protected Player p;
AimBox aimer;
public void Enable(Player p) {
if (!hookedEvents) {
OnPlayerClickEvent.Register(PlayerClickCallback, Priority.Low);
hookedEvents = true;
}
this.p = p;
p.ClearBlockchange();
if (p.Supports(CpeExt.PlayerClick)) {
p.Message(Name + " engaged, click to fire at will");
} else {
p.Blockchange += BlockClickCallback;
p.Message(Name + " engaged, fire at will");
aimer = new AimBox();
aimer.Hook(p);
}
}
public void Disable() {
p.aiming = false;
p.ClearBlockchange();
p.Message(Name + " disabled");
}
protected abstract void OnActivated(byte yaw, byte pitch, BlockID block);
static void BlockClickCallback(Player p, ushort x, ushort y, ushort z, BlockID block) {
Weapon weapon = p.weapon;
if (weapon == null) return;
p.RevertBlock(x, y, z);
// defer to player click handler
if (weapon.aimer == null) return;
if (!p.level.Config.Guns) { weapon.Disable(); return; }
if (!CommandParser.IsBlockAllowed(p, "use", block)) return;
weapon.OnActivated(p.Rot.RotY, p.Rot.HeadX, block);
}
static void PlayerClickCallback(Player p, MouseButton btn, MouseAction action,
ushort yaw, ushort pitch, byte entity,
ushort x, ushort y, ushort z, TargetBlockFace face) {
Weapon weapon = p.weapon;
if (weapon == null || action != MouseAction.Pressed) return;
if (!(btn == MouseButton.Left || btn == MouseButton.Right)) return;
if (!p.level.Config.Guns) { weapon.Disable(); return; }
BlockID held = p.RawHeldBlock;
if (!CommandParser.IsBlockAllowed(p, "use", held)) return;
weapon.OnActivated((byte)(yaw >> 8), (byte)(pitch >> 8), held);
}
protected static Player PlayerAt(Player p, Vec3U16 pos, bool skipSelf) {
Player[] players = PlayerInfo.Online.Items;
foreach (Player pl in players) {
if (pl.level != p.level) continue;
if (p == pl && skipSelf) continue;
if (Math.Abs(pl.Pos.BlockX - pos.X) <= 1
&& Math.Abs(pl.Pos.BlockY - pos.Y) <= 1
&& Math.Abs(pl.Pos.BlockZ - pos.Z) <= 1)
{
return pl;
}
}
return null;
}
}
public class AmmunitionData {
public BlockID block;
public Vec3U16 pos, start;
public Vec3F32 dir;
public bool moving = true;
// positions of all currently visible "trailing" blocks
public List<Vec3U16> visible = new List<Vec3U16>();
// position of all blocks this ammunition has touched/gone through
public List<Vec3U16> all = new List<Vec3U16>();
public int iterations;
public Vec3U16 PosAt(int i) {
Vec3U16 target;
target.X = (ushort)Math.Round(start.X + (double)(dir.X * i));
target.Y = (ushort)Math.Round(start.Y + (double)(dir.Y * i));
target.Z = (ushort)Math.Round(start.Z + (double)(dir.Z * i));
return target;
}
}
/// <summary> Manages the glass box around the player. Adjusts based on where player is looking. </summary>
internal sealed class AimBox {
Player player;
List<Vec3U16> lastGlass = new List<Vec3U16>();
List<Vec3U16> curGlass = new List<Vec3U16>();
public void Hook(Player p) {
player = p;
SchedulerTask task = new SchedulerTask(AimCallback, null, TimeSpan.Zero, true);
p.CriticalTasks.Add(task);
}
void AimCallback(SchedulerTask task) {
Player p = player;
if (p.aiming) { Update(); return; }
foreach (Vec3U16 pos in lastGlass) {
if (!p.level.IsValidPos(pos)) continue;
p.RevertBlock(pos.X, pos.Y, pos.Z);
}
task.Repeating = false;
}
void Update() {
Player p = player;
Vec3F32 dir = DirUtils.GetDirVector(p.Rot.RotY, p.Rot.HeadX);
ushort x = (ushort)Math.Round(p.Pos.BlockX + dir.X * 3);
ushort y = (ushort)Math.Round(p.Pos.BlockY + dir.Y * 3);
ushort z = (ushort)Math.Round(p.Pos.BlockZ + dir.Z * 3);
int dx = Math.Sign(dir.X) >= 0 ? 1 : -1, dz = Math.Sign(dir.Z) >= 0 ? 1 : -1;
Check(p.level, x, y, z );
Check(p.level, x + dx, y, z );
Check(p.level, x, y, z + dz);
Check(p.level, x + dx, y, z + dz);
// Revert all glass blocks now not in the ray from the player's direction
for (int i = 0; i < lastGlass.Count; i++) {
Vec3U16 pos = lastGlass[i];
if (curGlass.Contains(pos)) continue;
if (p.level.IsValidPos(pos))
p.RevertBlock(pos.X, pos.Y, pos.Z);
lastGlass.RemoveAt(i); i--;
}
// Place the new glass blocks that are in the ray from the player's direction
foreach (Vec3U16 pos in curGlass) {
if (lastGlass.Contains(pos)) continue;
lastGlass.Add(pos);
p.SendBlockchange(pos.X, pos.Y, pos.Z, Block.Glass);
}
curGlass.Clear();
}
void Check(Level lvl, int x, int y, int z) {
Vec3U16 pos = new Vec3U16((ushort)x, (ushort)(y - 1), (ushort)z);
if (lvl.IsAirAt(pos.X, pos.Y, pos.Z)) curGlass.Add(pos);
pos.Y++;
if (lvl.IsAirAt(pos.X, pos.Y, pos.Z)) curGlass.Add(pos);
}
}
}

View File

@ -80,7 +80,7 @@ namespace MCGalaxy {
BlockID block = (BlockID)(flags & blockMask);
bulkSender.Add(index, block);
}
bulkSender.Send(true);
bulkSender.Flush();
RemoveRange(0, count);
} catch (Exception e) {
Logger.LogError(e);

View File

@ -198,7 +198,7 @@ namespace MCGalaxy {
}
if (bulkSender != null)
bulkSender.Send(true);
bulkSender.Flush();
ListUpdate.Clear(); listUpdateExists.Clear();
}

View File

@ -192,9 +192,8 @@ namespace MCGalaxy {
Player[] players = PlayerInfo.Online.Items;
foreach (Player pl in players) {
if (pl.level != lvl || !pl.aiming) continue;
pl.aiming = false;
pl.ClearBlockchange();
if (pl.level != lvl || pl.weapon == null) continue;
pl.weapon.Disable();
}
}

View File

@ -505,6 +505,8 @@
<Compile Include="Games\TntWars\TWGame.Round.cs" />
<Compile Include="Games\TntWars\TWGame.cs" />
<Compile Include="Games\TntWars\TWConfig.cs" />
<Compile Include="Games\Weapons\Weapon.cs" />
<Compile Include="Games\Weapons\Guns.cs" />
<Compile Include="Games\ZombieSurvival\ZSGame.DB.cs" />
<Compile Include="Games\ZombieSurvival\ZSGame.Round.cs" />
<Compile Include="Games\ZombieSurvival\ZSGame.cs" />
@ -718,6 +720,7 @@
<Folder Include="Commands\Maintenance" />
<Folder Include="Config\Permissions" />
<Folder Include="Database\Stats" />
<Folder Include="Games\Weapons" />
<Folder Include="Games\RoundsGame" />
<Folder Include="Generator\Realistic" />
<Folder Include="Network\Heartbeat" />

View File

@ -39,13 +39,12 @@ namespace MCGalaxy.Network {
/// <summary> Constructs a bulk sender that will only send block changes to that player. </summary>
public BufferedBlockSender(Player player) {
this.player = player;
this.level = player.level;
this.level = player.level;
}
/// <summary> Adds a block change, and potentially sends block change packets if
/// number of buffered block changes has reached the limit. </summary>
/// <returns> Whether block change packets were actually sent. </returns>
public bool Add(int index, BlockID block) {
/// <summary> Adds a block change to list of buffered changes. </summary>
/// <remarks> When buffer limit is reached, calls Flush(), resetting buffered list. </remarks>
public void Add(int index, BlockID block) {
indices[count] = index;
if (Block.IsPhysicsType(block)) {
blocks[count] = Block.Convert(block);
@ -54,20 +53,16 @@ namespace MCGalaxy.Network {
}
count++;
return Send(false);
if (count == 256) Flush();
}
/// <summary> Sends the block change packets if either 'force' is true,
/// or the number of buffered block changes has reached the limit. </summary>
/// <returns> Whether block change packets were actually sent. </returns>
public bool Send(bool force) {
if (count > 0 && (force || count == 256)) {
if (player != null) SendPlayer();
else SendLevel();
count = 0;
return true;
}
return false;
/// <summary> Sends buffered block change packets to target player(s). </summary>
public void Flush() {
if (count == 0) return;
if (player != null) SendPlayer();
else SendLevel();
count = 0;
}
void SendLevel() {

View File

@ -126,6 +126,7 @@ namespace MCGalaxy {
public VolatileArray<SchedulerTask> CriticalTasks = new VolatileArray<SchedulerTask>();
public bool aiming;
public Weapon weapon;
public bool isFlying;
public bool joker;

View File

@ -239,7 +239,7 @@ namespace MCGalaxy {
LastAction = DateTime.UtcNow;
IsAfk = false;
isFlying = false;
aiming = false;
if (weapon != null) weapon.Disable();
if (chatMsg != null) chatMsg = Colors.Escape(chatMsg);
discMsg = Colors.Escape(discMsg);