diff --git a/README.md b/README.md index 4556509..2b722ba 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ # MafiaCraft -

MafiaCraft is a plugin for Spigot-based Minecraft servers that recreates the classic hidden roleData game +

MafiaCraft is a plugin for Spigot-based Minecraft servers that recreates the classic hidden role game Mafia (also played under the name Werewolf) into Minecraft. This plugin was heavily inspired by those -games, as well as other online hidden roleData games like Town of Salem.

+games, as well as other online hidden role games like Town of Salem.

This plugin is now heading into final testing, and then will be used on my server. Let's hope it works!

# Gameplay @@ -28,101 +28,101 @@ Listed below are all the roles and abilities planned, as well as some definition | *Godfather* | The head of the local crime family. | Protection,
Charisma | Tell the members of the Mafia what to do. | | Mafioso | A member of the local crime family. | Succession | Follow the orders of the Godfather. | | Framer | A skilled bookkeeper turned crime-staging genius. | Forgery | Follow the orders of the Godfather. | -| Assassin | The dedicated killer within the Mafia's ranks. | Assassination | Follow the orders of the Godfather. | +| Assassin | The dedicated killer within the Mafia's ranks. | Assassinate | Follow the orders of the Godfather. | ### Village Roles | Role | Description | Abilities | Tasks | |--------------|------------------------------------------------------------------------------------|-----------------------------------------|-----------------------------------------------------------| -| *Reanimator* | A Villager who discovered the long lost art of resurrection. | Reanimation | Reanimate players to help the Village survive. | -| Veteran | A war hero whose defense instincts never left when they retired from the military. | Retaliation | Defend yourself from attackers to help the Villagers win. | +| *Reanimator* | A Villager who discovered the long lost art of resurrection. | Revive | Reanimate players to help the Village survive. | +| Veteran | A war hero whose defense instincts never left when they retired from the military. | Retaliate | Defend yourself from attackers to help the Villagers win. | | Deputy | A fighter with honor and standards, who will always rise to a fair challenge. | High Noon,
Marksman | Take down those who the Village sees as a threat. | | Investigator | An armchair detective whose skills can finally be put to good use. | Investigate | Discover who is really on the side of the Villagers. | | Lookout | An eagle-eyed observer who always seems to know who's been where. | Watch,
Peripherals,
Clear Sight | Gather information to help the Village. | | Doctor | A skilled medic capable of helping those in need. | Rescue | Protect other Villagers. | -| *Apothecary* | A skilled brewer capable of producing special concoctions. | Ambrosia | Brew potions for the Village. | -| Deacon | A religious devotee who can sense the presence of unholy abilities. | Inquisition | Find out who in the town may be harboring a secret power. | +| *Apothecary* | A skilled brewer capable of producing special concoctions. | Ambrosia | Stop the spread of the Vampires and protect the Village. | +| Priest | A religious devotee who can sense the presence of unholy abilities. | Inquisition | Find out who in the town may be harboring a secret power. | ### Neutral Roles _Notes: -Alone and Same Role mean only themselves (or those who share their roleData) and those -who win by Surviving remaining alive. -Win by Surviving roles can win with anyone, as long as they survive until the end of the game._ - -| Role | Description | Abilities | Win Conditions | Tasks | -|---------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------|----------------|----------------------------------------------------------------------------| -| Serial Killer | An insane murderer who wants nothing more than to be alone. | Protection,
Ambush | Alone | Kill everyone without being caught. | -| Trapper | Some people just want to see the world burn. | This is Fine,
Dodge Roll | Alone | Use traps and fire to kill everyone without being caught. | -| Hunter | Ensure that the targets you are given die by the end of the game, and remain dead. | Target,
Tracking | Surviving* | Use any means necessary to ensure your target players die.

Avoid dying. | -| Sorcerer | A powerful spell-caster bound by no allegiances. | Scatter,
Toadify,
Fog of War,
Vanish,
Spell Book | Surviving | Use your abilities to survive. | -| *Werewolf* | A regular person who suddenly transforms under the Full Moon. | Transform,
Rampage,
Nemesis | Alone | Kill everyone without being caught. | -| *Vampire* | A mythical creature whose only goal is to spread their reach to as many people as possible. | Convert,
Night Owl,
Staked | Same Role | Convert or kill all others.

Do not get caught. | -| *Jester* | A clown who just wants to trick the Village into making a grave mistake. | Just a Prank | Surviving* | Be killed by a member of the Village without fighting back. | - -_* These roles only win if they complete their special task **AND** survive until the end of the game._ +Players who win Alone win only if there are no Mafia, Village, or Vampires remaining. +Vampires must kill all players with the alignments the Mafia, Village, or Alone. +Those with no alignment can win with any team, as long as their tasks and win conditions are complete._ + +| Role | Description | Abilities | Alignment | Tasks and Win Conditions | +|---------------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|-----------|-------------------------------------------------------------| +| Serial Killer | An insane murderer who wants nothing more than to be alone. | Protection,
Ambush | Alone | Kill everyone without being caught. | +| Bodyguard | Someone who's willing to put their life on the line to save another. | Protectee,
Tracking,
This is Fine,
Dodge Roll | None | Keep your protectee alive at any cost. | +| Hunter | Ensure that the targets you are given die by the end of the game, and remain dead. | Target,
Tracking | None | Use any means necessary to ensure your target players die. | +| *Sorcerer* | A powerful spell-caster bound by an unknown allegiance. | Scatter,
Toadify,
Fog of War,
Vanish,
Spell Book,
Shadow Pledge | None | Survive to see your secret allegiance win the game. | +| *Werewolf* | A regular person who suddenly transforms under the Full Moon. | Transform,
Rampage,
Nemesis | Alone | Kill everyone without being caught. | +| *Vampire* | A mythical creature whose only goal is to spread their reach to as many people as possible. | Convert,
Night Owl,
Staked | Vampires | Convert or kill all others.
Do not get caught. | +| Jester | A clown who just wants to trick the Village into making a grave mistake. | Just a Prank | None | Be killed by a member of the Village without fighting back. | ## Abilities -| Ability | Description | Cooldown | -|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------| -| Protection | When attacked first, receive Resistance II for 30 seconds. | Once per in-game day. | -| Charisma | Appear innocent to Investigators, despite being a member of the Mafia. | | -| Succession | When the Godfather dies, one Mafioso will be selected at random to become the new Godfather. | | -| Forgery | Drop a paper named *Forgery* on a player to make them appear suspicious to Investigators. Resets at the end of the in-game day. | | -| Assassination | When attacking first, gains Strength I and Speed I for 60 seconds. | Once per in-game day. | -| Reanimation | Throw a totem of undying into a soul flame. Then, choose a dead player to resurrect. | Once per in-game week. | -| Retaliation | When attacked first, receive Strength II and Resistance II for 30 seconds. | Once per in-game day. | -| High Noon | Receive Strength II for 60 seconds at noon. | Occurs automatically. | -| Marksman | Arrows do one additional heart of damage. | | -| Investigate | Use a spyglass to spy on a player for 10 seconds to learn if they are suspected of being in the Mafia. *Note this only applies to Mafia roles.* | | -| Watch | Can see invisible players when using a Spyglass. | | -| Peripherals | Can see invisible players within 20 blocks. | | -| Clear Sight | Not affected by Blindness. | | -| Rescue | If a player (other than the medic themselves), would otherwise die within 16 blocks of the medic, they will be placed at 0.5 hearts and receive Regeneration III for 15 seconds. | Once per in-game day. | -| Ambrosia | Brew a special splash potion by throwing a golden apple, bottle of honey, and bucket of milk into a heated cauldron. This will revert a Vampire or Werewolf back to their original roleData, unless that was their original roleData. | Can be brewed once per in-game week. | -| Inquisition | Any player who has is using or has recently used an Unholy Ability will display a subtle particle effect to Deacons. | | -| Ambush | When attacking first, gains Strength II for 30 seconds. | Once per in-game day. | -| This is Fine | Take reduced damage from fire and lava. | | -| Dodge Roll | Take significantly less damage from explosions and fall damage. | | -| Target | Receive a list at the beginning of the game of targets. | At the start of the game. | -| Tracking | See whether your targets are dead or alive. | | -| Spell Book | Craft a spell book by throwing any enchanted book into normal fire. | | -| Scatter | Using the spell book, effect one person with Speed IV for 20 seconds. Costs 5 levels of experience. | | -| Toadify | Using the spell book, effect one person with Slowness V and Jump Boost V for 15 seconds. Costs 5 levels of experience. | | -| Fog of War | Using the spell book, effect everyone within 30 blocks with Blindness I for 30 seconds. Costs 10 levels of experience. | | -| Vanish | Using the spell book, effect one person with Invisibility for 180 seconds. Costs 10 levels of experience. | | -| Transform | Transform on full-moon nights. | Occurs automatically. | -| Rampage | For every kill while Transformed, gain one level of strength for the rest of the night (maximum Strength V). | | -| Nemesis | Weak to Iron Tools while Transformed. | | -| Convert | All kills will not permanently kill the player, instead they will respawn as a Vampire. | | -| Night Owl | Affected by Weakness II during the day. | Occurs automatically. | -| Staked | Weak to Wooden Tools. | | -| Just a Prank | Any Villager who kills a Jester who does not fight back will be affected with Wither I indefinitely, until they are able to find a cure. The Jester will respawn. | | +| Ability | Description | Cooldown | +|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| Protection | When attacked first, receive Resistance II for 60 seconds. | Once every 3 in-game days. | +| Charisma | Appear innocent to Investigators, despite being a member of the Mafia. | | +| Succession | When the Godfather dies, one Mafioso will be selected at random to become the new Godfather. | | +| Forgery | Drop a paper named *Forgery* on a player to make them appear suspicious to Investigators. Resets at the end of the in-game day. | | +| Assassinate | When attacking first, gains Strength I and Speed I for 2 minutes. | Once every 3 in-game days. | +| Revive | Throw a Totem of Undying into a soul flame. Then, choose a dead player to resurrect. | Once every 24 (real) hours. | +| Retaliate | When attacked first, receive Strength II and Resistance II for 30 seconds. | Once every 3 in-game days. | +| High Noon | Receive Strength II for 30 seconds at noon of the in-game day. | Occurs automatically. | +| Marksman | Arrows do one additional heart of damage. | | +| Investigate | Use a spyglass to spy on a player for 10 seconds to learn if they are suspected of being in the Mafia. *Note this only applies to Mafia roles.* | | +| Watch | Can see invisible players when using a Spyglass. | | +| Peripherals | Can see invisible players within 20 blocks. | | +| Clear Sight | Not affected by Blindness or Darkness. | | +| Rescue | If a player (other than the medic themselves), would otherwise die within 16 blocks of the medic, they will be placed at 0.5 hearts and receive Regeneration III for 15 seconds. | Once every 3 in-game days. | +| Ambrosia | Brew a special splash potion by throwing a golden apple, bottle of honey, and bucket of milk into a heated cauldron. This will revert a Vampire back to their original role, unless that was their original role. | Can be *brewed* every 12 (real) hours. | +| Inquisition | Any player who has is using or has recently used an Unholy Ability will display a subtle particle effect to Priests. | | +| Ambush | When attacking first, gains Strength II for 60 seconds. | Once every 3 in-game days. | +| Protectee | Assigned one player to protect at the beginning of the game. | At the start of the game. | +| This is Fine | Take reduced damage from fire and lava. | | +| Dodge Roll | Take significantly less damage from explosions and fall damage. | | +| Target | Receive a list at the beginning of the game of targets. | At the start of the game. | +| Tracking | See whether your targets are dead or alive. | | +| Spell Book | Craft a spell book by throwing any enchanted book into normal fire. | | +| Scatter | Using the spell book, grant one person Speed IV for 20 seconds. Costs 4 levels of experience. | | +| Toadify | Using the spell book, inflict one person with Slowness V and Jump Boost V for 15 seconds. Costs 4 levels of experience. | | +| Fog of War | Using the spell book, inflict everyone within 30 blocks with Blindness I for 30 seconds. Costs 8 levels of experience. | | +| Vanish | Using the spell book, grant one person with Invisibility for 2 minutes. Costs 8 levels of experience. | | +| Shadow Pledge | At the beginning of the game, you are randomly assigned alignment with the Village or Mafia. You win with this team, but they do not know this. | At the start of the game. | +| Transform | Transform on full-moon nights. | Occurs automatically. | +| Rampage | For every kill while Transformed, gain one level of strength for the rest of the night (maximum Strength III). | | +| Nemesis | Weak to Iron Tools while Transformed. | | +| Convert | All kills will not permanently kill the player, instead they will respawn as a Vampire. | | +| Night Owl | Affected by Weakness II during the day. | Occurs automatically. | +| Staked | Weak to Wooden Tools. | | +| Just a Prank | Any Villager who kills a Jester who does not fight back will cause an explosion and various negative effects. The Jester will respawn and have their role revealed. | **One-time use.** | ## Rules and Definitions * **In-Game Day**: starts and ends at sunrise (tick 0) -* **Attack**: Hit a player with a Sword, Axe, or Bow. Punches do not count to prevent false triggers. +* **Attack**: Hit a player with a Sword, Axe, or Bow. Punches do not count (to prevent false triggers). * **Attacked First**: Be attacked when not attacking anyone for 30 seconds prior. * **Attacking First**: Attack someone who has not attacked anyone for 30 seconds. -* **Unholy Abilities**: Reanimation, Ambrosia, Spell Book, Scatter, Toadify, Fog of War, Vanish, Transform, Convert, Hunting Night - * Unholy Abilities display to Deacons for two complete in-game days (does not reset at sunrise). This is refreshed every time an unholy ability is used. +* **Unholy Abilities**: Revive, Ambrosia, Spell Book, Scatter, Toadify, Fog of War, Vanish, Transform, Convert, Hunting Night + * Unholy Abilities display to Priests for two complete in-game days (does not reset at sunrise). This is refreshed every time an unholy ability is used. * Wins by Surviving roles win with **anyone**, even Wins Alone roles. * The game will end at sundown, when the living players have met their win conditions (or failed to, in the case of some neutral roles) ## Commands -`/mafiacraft`: Available to all players. Shows information about their roleData. +`/mafiacraft`: Available to all players. Shows information about their role. `/mafiacraftadmin`: Available to operators only. Controls all aspects of MafiaCraft that are not controlled in the config. These commands also have aliases to make them easier to use quickly. ### The `/mafiacraftadmin` command tree -* `setrole `: Sets a player's roleData. The player must be online. No spaces allowed in roleData name. +* `setrole `: Sets a player's role. The player must be online. No spaces allowed in role name. * `removeplayer `: Removes the specified player from the game. The player must be online. * `randomize` - * `add `: Adds the player to the list of players who need their roleData randomized. The player must be online. Players can have their roleData re-randomized if they already have one. + * `add `: Adds the player to the list of players who need their role randomized. The player must be online. Players can have their role re-randomized if they already have one. * `addall`: Adds all players who have ever joined this world into the list of players to be randomized. The list is taken from the `world/playerdata` folder. * `remove `: Removes the specified player from the list of players to be randomized. Use UUID if the player is offline (see `/mafiacraft randomize list`). * `removeall`: Clears the list of players to be randomized. diff --git a/config.yml b/config.yml index a73c25a..8cebc55 100644 --- a/config.yml +++ b/config.yml @@ -7,6 +7,10 @@ hardcoreStyle: true # If true, players in spectator mode will not be able to send messages to chat. Operators bypass this. blockSpectatorChat: true +# If true, spectating players will appear to leave the game when they die and will be hidden from the tab list when online. +# Remember to set the game rule to hide player death messages if true! +hideDeadPlayers: false + # Amount of time in seconds players will be considered as "attacking" after attempting to kill someone attackDuration: 30 @@ -28,15 +32,15 @@ peripheralsRange: 10 rescueRange: 8.0 # Cost (in levels) to trigger Scatter -scatterCost: 5 +scatterCost: 4 # Cost (in levels) to trigger Toadify -toadifyCost: 15 +toadifyCost: 4 # Cost (in levels) to trigger Fog of War -fogOfWarCost: 10 +fogOfWarCost: 8 # Effective range of Fog of War fogOfWarRange: 30 # Cost (in levels) to trigger Vanish -vanishCost: 10 +vanishCost: 8 # Number of targets hunters will be given hunterNumTargets: 3 @@ -50,7 +54,7 @@ requiredRoles: - "REANIMATOR" - "INVESTIGATOR" - "APOTHECARY" - - "DEACON" + - "PRIEST" - "SERIAL_KILLER" - "HUNTER" diff --git a/plugin.yml b/plugin.yml index 6ee883c..36a6585 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,7 +1,7 @@ name: MafiaCraft main: mddev0.mafiacraft.MafiaCraft -version: 1.1 -api-version: 1.19 +version: b1.1.0 +api-version: 1.20 depend: [ProtocolLib] commands: mafiacraftadmin: diff --git a/src/mddev0/mafiacraft/MafiaCraft.java b/src/mddev0/mafiacraft/MafiaCraft.java index 8254b0e..2c56d6c 100644 --- a/src/mddev0/mafiacraft/MafiaCraft.java +++ b/src/mddev0/mafiacraft/MafiaCraft.java @@ -60,9 +60,9 @@ public void onEnable() { Bukkit.getPluginManager().registerEvents(new Protection(this), this); Bukkit.getPluginManager().registerEvents(new Succession(this), this); Bukkit.getPluginManager().registerEvents(new Forgery(this), this); - Bukkit.getPluginManager().registerEvents(new Assassination(this), this); - Bukkit.getPluginManager().registerEvents(new Reanimation(this), this); - Bukkit.getPluginManager().registerEvents(new Retaliation(this), this); + Bukkit.getPluginManager().registerEvents(new Assassinate(this), this); + Bukkit.getPluginManager().registerEvents(new Revive(this), this); + Bukkit.getPluginManager().registerEvents(new Retaliate(this), this); abilityHighNoon.runTaskTimer(this, 0L, 1L); // checks every tick Bukkit.getPluginManager().registerEvents(new Marksman(this), this); Bukkit.getPluginManager().registerEvents(new Investigate(this), this); @@ -72,7 +72,7 @@ public void onEnable() { Bukkit.getPluginManager().registerEvents(new Rescue(this), this); abilityAmbrosia.runTaskTimer(this, 0L, 100L); // checks every 5 seconds Bukkit.getPluginManager().registerEvents(abilityAmbrosia, this); - abilityInquisition.runTaskTimer(this, 0L, 600L); // show particles every 10 seconds + abilityInquisition.runTaskTimer(this, 0L, 200L); // show particles every 10 seconds Bukkit.getPluginManager().registerEvents(new Ambush(this), this); Bukkit.getPluginManager().registerEvents(new ThisIsFine(this), this); Bukkit.getPluginManager().registerEvents(new DodgeRoll(this), this); diff --git a/src/mddev0/mafiacraft/abilities/Ability.java b/src/mddev0/mafiacraft/abilities/Ability.java index 3eb83b9..a20a72f 100644 --- a/src/mddev0/mafiacraft/abilities/Ability.java +++ b/src/mddev0/mafiacraft/abilities/Ability.java @@ -2,47 +2,91 @@ @SuppressWarnings("unused") public enum Ability { - PROTECTION("Protection"), - CHARISMA("Charisma"), // Has no class - SUCCESSION("Succession"), - FORGERY("Forgery"), - ASSASSINATION("Assassination"), - REANIMATION("Reanimation"), - RETALIATION("Retaliation"), - HIGH_NOON("High Noon"), - MARKSMAN("Marksman"), - INVESTIGATE("Investigate"), - WATCH("Watch"), - PERIPHERALS("Peripherals"), - CLEAR_SIGHT("Clear Sight"), - RESCUE("Rescue"), - AMBROSIA("Ambrosia"), - INQUISITION("Inquisition"), - AMBUSH("Ambush"), - THIS_IS_FINE("This is Fine"), - DODGE_ROLL("Dodge Roll"), - TARGET("Target"), // Has no class - TRACKING("Tracking"), // Has no class - SPELL_BOOK("Spell Book"), - SCATTER("Scatter"), - TOADIFY("Toadify"), - FOG_OF_WAR("Fog of War"), - VANISH("Vanish"), - TRANSFORM("Transform"), - RAMPAGE("Rampage"), - NEMESIS("Nemesis"), - CONVERT("Convert"), - NIGHT_OWL("Night Owl"), - STAKED("Staked"), - JUST_A_PRANK("Just a Prank"); + PROTECTION("Protection", + "When attacked first, receive Resistance II for 30 seconds."), + CHARISMA("Charisma", + "Appear innocent to Investigators, despite being a member of the Mafia."), // Has no class + SUCCESSION("Succession", + "When the Godfather dies, one Mafioso will be selected at random to become the new Godfather."), + FORGERY("Forgery", + "Drop a paper named 'Forgery' on a player to make them appear suspicious to Investigators. Resets at the end of the in-game day."), + ASSASSINATE("Assassinate", + "When attacking first, gains Strength I and Speed I for 60 seconds."), + REVIVE("Revive", + "Throw a Totem of Undying into a soul flame. Then, choose a dead player to resurrect."), + RETALIATE("Retaliate", + "When attacked first, receive Strength II and Resistance II for 30 seconds."), + HIGH_NOON("High Noon", + "Receive Strength II for 60 seconds at noon of the in-game day."), + MARKSMAN("Marksman", + "Arrows do one additional heart of damage."), + INVESTIGATE("Investigate", + "Use a spyglass to spy on a player for 10 seconds to learn if they are suspected of being in the Mafia."), + WATCH("Watch", + "Can see invisible players when using a Spyglass."), + PERIPHERALS("Peripherals", + "Can see invisible players within 20 blocks."), + CLEAR_SIGHT("Clear Sight", + "Not affected by Blindness or Darkness."), + RESCUE("Rescue", + "If a player (other than you), would otherwise die within 16 blocks of you, they will be placed at 0.5 hearts and receive Regeneration III for 15 seconds."), + AMBROSIA("Ambrosia", + "Brew a special potion by throwing a Golden Apple, Bottle of Honey, and Bucket of Milk into a heated cauldron."), + INQUISITION("Inquisition", + "Any player who has is using or has recently used any magical ability will display a subtle particle effect to Priests."), + AMBUSH("Ambush", + "When attacking first, gains Strength II for 30 seconds."), + PROTECTEE("Protectee", + "Assigned one player to protect at the beginning of the game."), // Has no class + THIS_IS_FINE("This is Fine", + "Take reduced damage from fire and lava."), + DODGE_ROLL("Dodge Roll", + "Take significantly less damage from explosions and fall damage."), + TARGET("Target", + "Receive a list at the beginning of the game of targets."), // Has no class + TRACKING("Tracking", + "See whether your targets or protectee are dead or alive."), // Has no class + SPELL_BOOK("Spell Book", + "Craft a spell book by throwing any enchanted book into normal fire. Use to switch spells."), + SCATTER("Scatter", + "Grant one person Speed IV for 20 seconds. Costs 5 levels of experience."), + TOADIFY("Toadify", + "Inflict one person with Slowness V and Jump Boost V for 15 seconds. Costs 5 levels of experience."), + FOG_OF_WAR("Fog of War", + "Inflict everyone within 30 blocks with Blindness I for 30 seconds. Costs 10 levels of experience."), + VANISH("Vanish", + "Grant one person Invisibility for 3 minutes. Costs 10 levels of experience."), + SHADOW_PLEDGE("Shadow Pledge", + "At the beginning of the game, you are randomly assigned alignment with the Village or Mafia. You win with this team, but they do not know this."), // Has no class + TRANSFORM("Transform", + "Transform on full-moon nights."), + RAMPAGE("Rampage", + "For every kill while Transformed, gain one level of strength for the rest of the night (maximum Strength V)."), + NEMESIS("Nemesis", + "Weak to Iron Tools while Transformed."), + CONVERT("Convert", + "All kills will not permanently kill the player, instead they will respawn as a Vampire."), + NIGHT_OWL("Night Owl", + "Affected by Weakness II during the day."), + STAKED("Staked", + "Weak to Wooden Tools."), + JUST_A_PRANK("Just a Prank", + "Be killed by a member of the Village without fighting back to respawn."); private final String NAME; - Ability(String name) { + private final String DESCRIPTION; + + Ability(String name, String desc) { this.NAME = name; + this.DESCRIPTION = desc; } - public String fullName() { + public String toString() { return NAME; } + + public String getDesc() { + return DESCRIPTION; + } } diff --git a/src/mddev0/mafiacraft/abilities/Ambrosia.java b/src/mddev0/mafiacraft/abilities/Ambrosia.java index ed69dee..951c604 100644 --- a/src/mddev0/mafiacraft/abilities/Ambrosia.java +++ b/src/mddev0/mafiacraft/abilities/Ambrosia.java @@ -49,7 +49,10 @@ public void run() { if (b.getType() == Material.WATER_CAULDRON) { // check block below if (i.getLocation().add(0, -1, 0).getBlock().getType() == Material.FIRE || - i.getLocation().add(0, -1, 0).getBlock().getType() == Material.SOUL_FIRE) { + i.getLocation().add(0, -1, 0).getBlock().getType() == Material.SOUL_FIRE || + i.getLocation().add(0, -1, 0).getBlock().getType() == Material.CAMPFIRE || + i.getLocation().add(0, -1, 0).getBlock().getType() == Material.SOUL_CAMPFIRE || + i.getLocation().add(0, -1, 0).getBlock().getType() == Material.LAVA) { // Check cauldron level Levelled bdata = (Levelled) b.getBlockData(); if (bdata.getLevel() == bdata.getMaximumLevel()) { @@ -80,16 +83,14 @@ public void run() { ambrosiaPotion.setColor(Color.fromRGB(255,200,0)); ambrosiaPotion.setDisplayName(ChatColor.GOLD + "Ambrosia"); List ambrosiaLore = new ArrayList<>(); - ambrosiaLore.add("Splash on any " + ChatColor.DARK_RED + "Werewolf" + - ChatColor.RESET + " or " + ChatColor.DARK_PURPLE + "Vampire"); + ambrosiaLore.add("Splash on any " + ChatColor.DARK_PURPLE + "Vampire"); ambrosiaLore.add("to convert them back to their original role."); ambrosiaPotion.setLore(ambrosiaLore); ambrosiaItem.setItemMeta(ambrosiaPotion); Item spawned = (Item) i.getWorld().spawnEntity(b.getLocation(), EntityType.DROPPED_ITEM); spawned.setItemStack(ambrosiaItem); // Add cooldown to thrower - long waitUntil = plugin.getWorldFullTime() + (24000L * 7); // 7 days later - waitUntil = waitUntil - (waitUntil % 24000); // Round to earliest morning + long waitUntil = plugin.getWorldFullTime() + 864000L; // 12 real hours later thrower.getCooldowns().startCooldown(Ability.AMBROSIA, waitUntil); thrower.getStatus().startStatus(StatusData.Status.UNHOLY, plugin.getWorldFullTime() + 48000L); // Two days of unholy } @@ -102,7 +103,6 @@ public void run() { } } - // TODO: Make this take werewolves out of transformation? // When potion lands @EventHandler public void onPotionLand(PotionSplashEvent splash) { diff --git a/src/mddev0/mafiacraft/abilities/Ambush.java b/src/mddev0/mafiacraft/abilities/Ambush.java index 4f37541..275baf0 100644 --- a/src/mddev0/mafiacraft/abilities/Ambush.java +++ b/src/mddev0/mafiacraft/abilities/Ambush.java @@ -33,8 +33,8 @@ public void onAttackPlayer(EntityDamageByEntityEvent damage) { if (damager != null) { // Only take action if not null if (damagerMP.getRole().getAbilities().contains(Ability.AMBUSH) && !damagerMP.getCooldowns().isOnCooldown(Ability.AMBUSH)) { - damager.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 600, 1, false, false, true)); - long waitUntil = plugin.getWorldFullTime() + 24000L; + damager.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 1200, 1, false, false, true)); + long waitUntil = plugin.getWorldFullTime() + 24000L * 3; waitUntil = waitUntil - (waitUntil % 24000); damagerMP.getCooldowns().startCooldown(Ability.AMBUSH, waitUntil); } diff --git a/src/mddev0/mafiacraft/abilities/Assassination.java b/src/mddev0/mafiacraft/abilities/Assassinate.java similarity index 82% rename from src/mddev0/mafiacraft/abilities/Assassination.java rename to src/mddev0/mafiacraft/abilities/Assassinate.java index 9633373..0e6f643 100644 --- a/src/mddev0/mafiacraft/abilities/Assassination.java +++ b/src/mddev0/mafiacraft/abilities/Assassinate.java @@ -12,11 +12,11 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -public final class Assassination implements Listener { +public final class Assassinate implements Listener { private final MafiaCraft plugin; - public Assassination(MafiaCraft plugin) { + public Assassinate(MafiaCraft plugin) { this.plugin = plugin; } @@ -31,12 +31,12 @@ public void onAttackPlayer(EntityDamageByEntityEvent damage) { if (damager != null) { // Only take action if not null MafiaPlayer attacker = plugin.getLivingPlayers().get(damager.getUniqueId()); - if (attacker != null && attacker.getRole().getAbilities().contains(Ability.ASSASSINATION) && !attacker.getCooldowns().isOnCooldown(Ability.ASSASSINATION)) { - damager.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 1200, 0, false, false, true)); - damager.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 1200, 0, false, false, true)); - long waitUntil = plugin.getWorldFullTime() + 24000L; + if (attacker != null && attacker.getRole().getAbilities().contains(Ability.ASSASSINATE) && !attacker.getCooldowns().isOnCooldown(Ability.ASSASSINATE)) { + damager.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 2400, 0, false, false, true)); + damager.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 2400, 0, false, false, true)); + long waitUntil = plugin.getWorldFullTime() + 24000L * 3; waitUntil = waitUntil - (waitUntil % 24000); - attacker.getCooldowns().startCooldown(Ability.ASSASSINATION, waitUntil); + attacker.getCooldowns().startCooldown(Ability.ASSASSINATE, waitUntil); } } } diff --git a/src/mddev0/mafiacraft/abilities/ClearSight.java b/src/mddev0/mafiacraft/abilities/ClearSight.java index e4fd642..a12a317 100644 --- a/src/mddev0/mafiacraft/abilities/ClearSight.java +++ b/src/mddev0/mafiacraft/abilities/ClearSight.java @@ -8,8 +8,6 @@ import org.bukkit.event.entity.EntityPotionEffectEvent; import org.bukkit.potion.PotionEffectType; -import java.util.Objects; - public final class ClearSight implements Listener { private final MafiaCraft plugin; @@ -20,12 +18,14 @@ public ClearSight(MafiaCraft plugin) { @SuppressWarnings("unused") @EventHandler - public void onBlindnessApplied(EntityPotionEffectEvent potion) { + public void onEffectApplied(EntityPotionEffectEvent potion) { if (!plugin.getActive()) return; // DO NOTHING IF NOT ACTIVE! MafiaPlayer affected = plugin.getPlayerList().get(potion.getEntity().getUniqueId()); - if (potion.getEntity().getType() == EntityType.PLAYER && affected != null && - affected.getRole().getAbilities().contains(Ability.CLEAR_SIGHT) && - Objects.requireNonNull(potion.getNewEffect()).getType() == PotionEffectType.BLINDNESS) + if (potion.getEntity().getType() == EntityType.PLAYER && affected != null && potion.getNewEffect() != null && + affected.getRole().getAbilities().contains(Ability.CLEAR_SIGHT) && ( + potion.getNewEffect().getType() == PotionEffectType.BLINDNESS || + potion.getNewEffect().getType() == PotionEffectType.DARKNESS + )) potion.setCancelled(true); // Cancel potion effect if eligible player is being blinded } } diff --git a/src/mddev0/mafiacraft/abilities/HighNoon.java b/src/mddev0/mafiacraft/abilities/HighNoon.java index 28de090..f1da811 100644 --- a/src/mddev0/mafiacraft/abilities/HighNoon.java +++ b/src/mddev0/mafiacraft/abilities/HighNoon.java @@ -30,7 +30,7 @@ public void run() { if (p.getValue().getRole().getAbilities().contains(Ability.HIGH_NOON)) { Player affected = Bukkit.getPlayer(p.getKey()); if (affected != null && affected.isOnline()) { - affected.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 1200, 1, false, false, true)); + affected.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 600, 1, false, false, true)); } } } diff --git a/src/mddev0/mafiacraft/abilities/Inquisition.java b/src/mddev0/mafiacraft/abilities/Inquisition.java index 0134f78..276711d 100644 --- a/src/mddev0/mafiacraft/abilities/Inquisition.java +++ b/src/mddev0/mafiacraft/abilities/Inquisition.java @@ -30,8 +30,8 @@ public void run() { if (target.getValue().getStatus().hasStatus(StatusData.Status.UNHOLY)) { Player t = Bukkit.getPlayer(target.getKey()); assert t != null; - s.spawnParticle(Particle.TOWN_AURA, t.getLocation().add(0,1,0), 20, 1, 1, 1); - s.spawnParticle(Particle.SCULK_SOUL, t.getLocation().add(0,1,0), 2, 2, 2, 2); + s.spawnParticle(Particle.TOWN_AURA, t.getLocation().add(0,1,0), 15, 0.7, 0.7, 0.7, 0.0); + s.spawnParticle(Particle.SCULK_SOUL, t.getLocation().add(0,1,0), 2, 1.5, 1.5, 1.5, 0.0); } } } diff --git a/src/mddev0/mafiacraft/abilities/JustAPrank.java b/src/mddev0/mafiacraft/abilities/JustAPrank.java index bd1aba8..5684c58 100644 --- a/src/mddev0/mafiacraft/abilities/JustAPrank.java +++ b/src/mddev0/mafiacraft/abilities/JustAPrank.java @@ -15,6 +15,8 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import java.util.logging.Level; + public final class JustAPrank implements Listener { private final MafiaCraft plugin; @@ -30,18 +32,30 @@ public void onJesterKill(PlayerDeathEvent death) { if (death.getEntity().getKiller() != null) { MafiaPlayer jest = plugin.getPlayerList().get(death.getEntity().getUniqueId()); MafiaPlayer killer = plugin.getLivingPlayers().get(death.getEntity().getKiller().getUniqueId()); - if (jest != null && jest.getRole().getAbilities().contains(Ability.JUST_A_PRANK) && killer != null && killer.getRole().getAlignment() == Role.Team.VILLAGE) { + if (jest != null && jest.getRole().getAbilities().contains(Ability.JUST_A_PRANK) && + !(Boolean)jest.getRoleData().getData(RoleData.DataType.JESTER_ABILITY_USED) && + killer != null && killer.getRole().getAlignment() == Role.Team.VILLAGE) { if (!jest.getStatus().hasStatus(StatusData.Status.IN_COMBAT)) { + // For Jester jest.makeAlive(); Player jester = Bukkit.getPlayer(jest.getID()); assert jester != null; // jester must be online to have been killed jester.sendMessage(ChatColor.DARK_GREEN + "You were killed by a member of the Village! You've triggered your effect!"); jest.getRoleData().setData(RoleData.DataType.JESTER_ABILITY_USED, true); - // TODO: MAKE THIS MORE LETHAL! See notes. + death.setKeepInventory(true); + death.setKeepLevel(true); + // For other player Player toAffect = death.getEntity().getKiller(); - toAffect.sendMessage(ChatColor.LIGHT_PURPLE + "You killed the Jester! " + ChatColor.DARK_GRAY + "Find some milk to cure their effect!"); - toAffect.addPotionEffect(new PotionEffect(PotionEffectType.WITHER, Integer.MAX_VALUE, 0, false, true, true)); + toAffect.sendMessage(ChatColor.GRAY + "You killed the " + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.GRAY + "!"); + jester.getWorld().createExplosion(jester.getLocation(), 3.0f, false, false, jester); + toAffect.addPotionEffect(new PotionEffect(PotionEffectType.WITHER, PotionEffect.INFINITE_DURATION, 2, false, true, true)); toAffect.getWorld().spawnParticle(Particle.VILLAGER_ANGRY, toAffect.getLocation().add(0, 1, 0), 10, 1, 2, 1); + // For all others + jester.getWorld().strikeLightningEffect(jester.getLocation()); + plugin.getServer().broadcastMessage(jester.getName() + " has been revealed as the " + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.RESET + "!"); + plugin.getLogger().log(Level.INFO, "The jester (" + jester.getName() + ") was killed by " + toAffect.getName()); + jester.setDisplayName("[" + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.RESET + "] " + jester.getDisplayName()); + jester.setPlayerListName("[" + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.RESET + "] " + jester.getPlayerListName()); } } } diff --git a/src/mddev0/mafiacraft/abilities/Marksman.java b/src/mddev0/mafiacraft/abilities/Marksman.java index 705fc9e..807c72c 100644 --- a/src/mddev0/mafiacraft/abilities/Marksman.java +++ b/src/mddev0/mafiacraft/abilities/Marksman.java @@ -20,11 +20,10 @@ public Marksman(MafiaCraft plugin) { @EventHandler public void onArrowShoot(EntityShootBowEvent shot) { if (!plugin.getActive()) return; // DO NOTHING IF NOT ACTIVE! - if (shot.getEntity().getType() != EntityType.ARROW) return; + if (shot.getEntity().getType() != EntityType.PLAYER) return; // Only shots from players allowed MafiaPlayer shooter = plugin.getLivingPlayers().get(shot.getEntity().getUniqueId()); - if (shooter != null && shooter.getRole().getAbilities().contains(Ability.MARKSMAN) - && shot.getProjectile().getType() != EntityType.ARROW) { + if (shooter != null && shooter.getRole().getAbilities().contains(Ability.MARKSMAN)) { Arrow arrow = (Arrow) shot.getProjectile(); arrow.setDamage(arrow.getDamage() + 2.0); // Add one heart // SCUFFED: Probably make that a config value at some point diff --git a/src/mddev0/mafiacraft/abilities/Peripherals.java b/src/mddev0/mafiacraft/abilities/Peripherals.java index c80e0f7..302b924 100644 --- a/src/mddev0/mafiacraft/abilities/Peripherals.java +++ b/src/mddev0/mafiacraft/abilities/Peripherals.java @@ -3,7 +3,10 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.WrappedDataValue; import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.google.common.collect.Lists; import mddev0.mafiacraft.MafiaCraft; import mddev0.mafiacraft.player.MafiaPlayer; import org.bukkit.Bukkit; @@ -15,6 +18,7 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.potion.PotionEffectType; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -35,37 +39,45 @@ public Peripherals(MafiaCraft plugin, ProtocolManager manager) { public void onPlayerApproach(PlayerMoveEvent move) { if (!plugin.getActive()) return; // DO NOTHING IF NOT ACTIVE! // Scuffed: Yeah it just checks all players every time someone moves. "This is terrible. Oh well." - for (Map.Entry executor : plugin.getLivingPlayers().entrySet()) { - if (!Bukkit.getOfflinePlayer(executor.getKey()).isOnline()) continue; // Do nothing for offline players! - if (executor.getValue().getRole().getAbilities().contains(Ability.PERIPHERALS)) { + for (Map.Entry viewer : plugin.getLivingPlayers().entrySet()) { + if (!Bukkit.getOfflinePlayer(viewer.getKey()).isOnline()) continue; // Do nothing for offline players! + if (viewer.getValue().getRole().getAbilities().contains(Ability.PERIPHERALS)) { // Reset all players first for (Player p : plugin.getServer().getOnlinePlayers()) { if (p.hasPotionEffect(PotionEffectType.INVISIBILITY)) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - packet.getIntegers().write(0, p.getEntityId()); - WrappedDataWatcher watcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class); - watcher.setEntity(p); - watcher.setObject(0, serializer, (byte) (0x20)); // 0x20 set invis - packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects()); - manager.sendServerPacket(Bukkit.getPlayer(executor.getKey()), packet); + PacketContainer packet = manager.createPacket(PacketType.Play.Server.ENTITY_METADATA); // Create packet + packet.getIntegers().write(0, p.getEntityId()); // Set player to modify + + // Lists which contain metadata values to modify + // SEE: + // https://wiki.vg/Entity_metadata#Entity_Metadata + // https://github.com/dmulloy2/ProtocolLib/issues/2032 + StructureModifier> watchableAccessor = packet.getDataValueCollectionModifier(); + List values = Lists.newArrayList( + new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte)0x20) // 0x20 sets as invisible + ); + + watchableAccessor.write(0, values); + manager.sendServerPacket(Bukkit.getPlayer(viewer.getKey()), packet); } } // Now remove invis for nearby players int range = plugin.getConfig().getInt("peripheralsRange"); - for (Entity ent : Objects.requireNonNull(Bukkit.getPlayer(executor.getKey())) + for (Entity ent : Objects.requireNonNull(Bukkit.getPlayer(viewer.getKey())) .getNearbyEntities(range,range,range)) { if (ent.getType() != EntityType.PLAYER) continue; Player found = (Player) ent; if (found.hasPotionEffect(PotionEffectType.INVISIBILITY)) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - packet.getIntegers().write(0, found.getEntityId()); - WrappedDataWatcher watcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class); - watcher.setEntity(found); - watcher.setObject(0, serializer, (byte) (0)); // 0 remove invis - packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects()); - manager.sendServerPacket(Bukkit.getPlayer(executor.getKey()), packet); + PacketContainer packet = manager.createPacket(PacketType.Play.Server.ENTITY_METADATA); // Create packet + packet.getIntegers().write(0, ent.getEntityId()); // Set player to modify + + StructureModifier> watchableAccessor = packet.getDataValueCollectionModifier(); + List values = Lists.newArrayList( + new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte)0x00) // 0 sets as visible + ); + + watchableAccessor.write(0, values); + manager.sendServerPacket(Bukkit.getPlayer(viewer.getKey()), packet); } } } diff --git a/src/mddev0/mafiacraft/abilities/Protection.java b/src/mddev0/mafiacraft/abilities/Protection.java index 6b00c36..1f7fd7f 100644 --- a/src/mddev0/mafiacraft/abilities/Protection.java +++ b/src/mddev0/mafiacraft/abilities/Protection.java @@ -33,8 +33,8 @@ public void onPlayerAttacked(EntityDamageByEntityEvent damage) { if (CombatState.findAttackingPlayer(damage) != null) { // TRIGGER ABILITY Player toProtect = (Player) damage.getEntity(); - toProtect.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,600,1,false,false,true)); - long waitUntil = plugin.getWorldFullTime() + 24000L; + toProtect.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,1200,1,false,false,true)); + long waitUntil = plugin.getWorldFullTime() + 24000L * 3; waitUntil = waitUntil - (waitUntil % 24000); attacked.getCooldowns().startCooldown(Ability.PROTECTION, waitUntil); } diff --git a/src/mddev0/mafiacraft/abilities/Rampage.java b/src/mddev0/mafiacraft/abilities/Rampage.java index 17844c1..8e36550 100644 --- a/src/mddev0/mafiacraft/abilities/Rampage.java +++ b/src/mddev0/mafiacraft/abilities/Rampage.java @@ -46,7 +46,7 @@ public void run() { for (MafiaPlayer p : plugin.getLivingPlayers().values()) { if (p.getRole().getAbilities().contains(Ability.RAMPAGE)) { if ((Boolean)p.getRoleData().getData(RoleData.DataType.WEREWOLF_TRANSFORM)) { - int level = Math.min(5, (Integer)p.getRoleData().getData(RoleData.DataType.WEREWOLF_KILLS) - 1); + int level = Math.min(3, (Integer)p.getRoleData().getData(RoleData.DataType.WEREWOLF_KILLS)) - 1; if (level >= 0) Objects.requireNonNull(Bukkit.getPlayer(p.getID())).addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 120, level, false, false, true)); } diff --git a/src/mddev0/mafiacraft/abilities/Rescue.java b/src/mddev0/mafiacraft/abilities/Rescue.java index df712c8..2c2f53e 100644 --- a/src/mddev0/mafiacraft/abilities/Rescue.java +++ b/src/mddev0/mafiacraft/abilities/Rescue.java @@ -1,6 +1,7 @@ package mddev0.mafiacraft.abilities; import mddev0.mafiacraft.MafiaCraft; +import mddev0.mafiacraft.player.MafiaPlayer; import org.bukkit.ChatColor; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -33,8 +34,10 @@ public void onPlayerDamaged(EntityDamageEvent damage) { double range = plugin.getConfig().getDouble("rescueRange"); List nearbyEntities = damaged.getNearbyEntities(range,range,range); for (Entity e : nearbyEntities) { + MafiaPlayer rescuer = plugin.getLivingPlayers().get(e.getUniqueId()); if (e.getType() == EntityType.PLAYER && - plugin.getLivingPlayers().get(e.getUniqueId()).getRole().getAbilities().contains(Ability.RESCUE)) { + rescuer.getRole().getAbilities().contains(Ability.RESCUE) && + !rescuer.getCooldowns().isOnCooldown(Ability.RESCUE)) { // a player nearby has the rescue ability if (e.getUniqueId() != damaged.getUniqueId()) { // Can't dataMap yourself // the player is able to be saved @@ -45,9 +48,9 @@ public void onPlayerDamaged(EntityDamageEvent damage) { damaged.sendMessage(ChatColor.AQUA + "You were saved from death!"); // Handle the rescuer e.sendMessage(ChatColor.AQUA + "You saved " + ChatColor.GREEN + damaged.getName() + ChatColor.AQUA + " from death!"); - long waitUntil = plugin.getWorldFullTime() + 24000L; + long waitUntil = plugin.getWorldFullTime() + 24000L * 3; waitUntil = waitUntil - (waitUntil % 24000); // Cooldown ends at dawn - plugin.getLivingPlayers().get(e.getUniqueId()).getCooldowns().startCooldown(Ability.RESCUE, waitUntil); + rescuer.getCooldowns().startCooldown(Ability.RESCUE, waitUntil); } } } diff --git a/src/mddev0/mafiacraft/abilities/Retaliation.java b/src/mddev0/mafiacraft/abilities/Retaliate.java similarity index 89% rename from src/mddev0/mafiacraft/abilities/Retaliation.java rename to src/mddev0/mafiacraft/abilities/Retaliate.java index 43b5f4e..4448f58 100644 --- a/src/mddev0/mafiacraft/abilities/Retaliation.java +++ b/src/mddev0/mafiacraft/abilities/Retaliate.java @@ -12,11 +12,11 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -public final class Retaliation implements Listener { +public final class Retaliate implements Listener { private final MafiaCraft plugin; - public Retaliation(MafiaCraft plugin) { + public Retaliate(MafiaCraft plugin) { this.plugin = plugin; } @@ -26,18 +26,18 @@ public void onPlayerAttacked(EntityDamageByEntityEvent damage) { if (!plugin.getActive()) return; // DO NOTHING IF NOT ACTIVE! MafiaPlayer attacked = plugin.getLivingPlayers().get(damage.getEntity().getUniqueId()); if (damage.getEntityType() == EntityType.PLAYER && attacked != null && - attacked.getRole().getAbilities().contains(Ability.RETALIATION)) { - // Player attacked has retaliation ability - if (!attacked.getStatus().hasStatus(StatusData.Status.IN_COMBAT) && !attacked.getCooldowns().isOnCooldown(Ability.RETALIATION)) { + attacked.getRole().getAbilities().contains(Ability.RETALIATE)) { + // Player attacked has Retaliate ability + if (!attacked.getStatus().hasStatus(StatusData.Status.IN_COMBAT) && !attacked.getCooldowns().isOnCooldown(Ability.RETALIATE)) { // Does not trigger if player was the attacker or if ability is on cooldown if (CombatState.findAttackingPlayer(damage) != null) { // was attacked by a player Player toRetaliate = (Player) damage.getEntity(); toRetaliate.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,600,1,false,false,true)); toRetaliate.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE,600,1,false,false,true)); - long waitUntil = plugin.getWorldFullTime() + 24000L; + long waitUntil = plugin.getWorldFullTime() + 24000L * 3; waitUntil = waitUntil - (waitUntil % 24000); // Cooldown ends at dawn - attacked.getCooldowns().startCooldown(Ability.RETALIATION, waitUntil); + attacked.getCooldowns().startCooldown(Ability.RETALIATE, waitUntil); } } } diff --git a/src/mddev0/mafiacraft/abilities/Reanimation.java b/src/mddev0/mafiacraft/abilities/Revive.java similarity index 78% rename from src/mddev0/mafiacraft/abilities/Reanimation.java rename to src/mddev0/mafiacraft/abilities/Revive.java index 8d7524d..1cced6d 100644 --- a/src/mddev0/mafiacraft/abilities/Reanimation.java +++ b/src/mddev0/mafiacraft/abilities/Revive.java @@ -1,7 +1,7 @@ package mddev0.mafiacraft.abilities; import mddev0.mafiacraft.MafiaCraft; -import mddev0.mafiacraft.gui.ReanimationGUI; +import mddev0.mafiacraft.gui.ReviveGUI; import mddev0.mafiacraft.player.MafiaPlayer; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -13,12 +13,12 @@ import java.util.Objects; -// XXX: There is probably a bug somewhere in the Reanimation ability. We'll see during testing -public final class Reanimation implements Listener { +// XXX: There is probably a bug somewhere in the Revive ability. We'll see during testing +public final class Revive implements Listener { private final MafiaCraft plugin; - public Reanimation(MafiaCraft plugin) { + public Revive(MafiaCraft plugin) { this.plugin = plugin; } @@ -32,9 +32,9 @@ public void onItemDestroy(EntityCombustByBlockEvent damage) { // Valid item, check player and cooldown if (item.getThrower() == null) return; MafiaPlayer thrower = plugin.getLivingPlayers().get(item.getThrower()); - if (thrower.getRole().getAbilities().contains(Ability.REANIMATION) && !thrower.getCooldowns().isOnCooldown(Ability.REANIMATION)) { + if (thrower.getRole().getAbilities().contains(Ability.REVIVE) && !thrower.getCooldowns().isOnCooldown(Ability.REVIVE)) { item.remove(); - ReanimationGUI gui = new ReanimationGUI(plugin); + ReviveGUI gui = new ReviveGUI(plugin); gui.open(Objects.requireNonNull(Bukkit.getPlayer(item.getThrower()))); } } diff --git a/src/mddev0/mafiacraft/abilities/SpellBook.java b/src/mddev0/mafiacraft/abilities/SpellBook.java index 55f1b60..a2afdf8 100644 --- a/src/mddev0/mafiacraft/abilities/SpellBook.java +++ b/src/mddev0/mafiacraft/abilities/SpellBook.java @@ -77,10 +77,11 @@ public void onBookClick(PlayerInteractEvent click) { selectedIndex += 1; selectedIndex %= abilities.size(); sorcerer.getRoleData().setData(RoleData.DataType.SORCERER_SELECTED, abilities.stream().toList().get(selectedIndex)); - } while (sorcerer.getRoleData().getData(RoleData.DataType.SORCERER_SELECTED) == Ability.SPELL_BOOK); + } while (sorcerer.getRoleData().getData(RoleData.DataType.SORCERER_SELECTED) == Ability.SPELL_BOOK || + sorcerer.getRoleData().getData(RoleData.DataType.SORCERER_SELECTED) == Ability.SHADOW_PLEDGE); click.getPlayer().sendMessage(ChatColor.LIGHT_PURPLE + "Changed spells to " + ChatColor.GRAY + - ((Ability) sorcerer.getRoleData().getData(RoleData.DataType.SORCERER_SELECTED)).fullName()); + (sorcerer.getRoleData().getData(RoleData.DataType.SORCERER_SELECTED)).toString()); } } } diff --git a/src/mddev0/mafiacraft/abilities/Vanish.java b/src/mddev0/mafiacraft/abilities/Vanish.java index e5eec1a..c3b84b6 100644 --- a/src/mddev0/mafiacraft/abilities/Vanish.java +++ b/src/mddev0/mafiacraft/abilities/Vanish.java @@ -49,7 +49,7 @@ public void onItemSwing(PlayerInteractEvent click) { } if (p == null) p = click.getPlayer(); // apply effect - p.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 3600, 0, false, false, true)); + p.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 2400, 0, false, false, true)); click.getPlayer().setLevel(click.getPlayer().getLevel() - plugin.getConfig().getInt("vanishCost")); click.getPlayer().sendMessage(ChatColor.GREEN + "You used " + ChatColor.LIGHT_PURPLE + "Vanish" + ChatColor.GREEN + " on " + ChatColor.AQUA + p.getName()); diff --git a/src/mddev0/mafiacraft/abilities/Watch.java b/src/mddev0/mafiacraft/abilities/Watch.java index 0add08b..5b9054e 100644 --- a/src/mddev0/mafiacraft/abilities/Watch.java +++ b/src/mddev0/mafiacraft/abilities/Watch.java @@ -3,7 +3,10 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.WrappedDataValue; import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.google.common.collect.Lists; import mddev0.mafiacraft.MafiaCraft; import mddev0.mafiacraft.player.MafiaPlayer; import org.bukkit.Material; @@ -15,6 +18,8 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitRunnable; +import java.util.List; + public final class Watch implements Listener { private final MafiaCraft plugin; @@ -48,13 +53,19 @@ public void run() { // if spyglass is active, keep setting all players inside the spyglass as visible for (Player p : plugin.getServer().getOnlinePlayers()) { if (!p.equals(click.getPlayer()) && p.hasPotionEffect(PotionEffectType.INVISIBILITY)) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - packet.getIntegers().write(0, p.getEntityId()); - WrappedDataWatcher watcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class); - watcher.setEntity(p); - watcher.setObject(0, serializer, (byte) (0)); // 0 remove invis - packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects()); + PacketContainer packet = manager.createPacket(PacketType.Play.Server.ENTITY_METADATA); // Create packet + packet.getIntegers().write(0, p.getEntityId()); // Set player to modify + + // Lists which contain metadata values to modify + // SEE: + // https://wiki.vg/Entity_metadata#Entity_Metadata + // https://github.com/dmulloy2/ProtocolLib/issues/2032 + StructureModifier> watchableAccessor = packet.getDataValueCollectionModifier(); + List values = Lists.newArrayList( + new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte)0x00) // 0 sets as visible + ); + + watchableAccessor.write(0, values); manager.sendServerPacket(click.getPlayer(), packet); } } @@ -62,13 +73,15 @@ public void run() { // spyglass is down, reset all invis players to invis for (Player p : plugin.getServer().getOnlinePlayers()) { if (!p.equals(click.getPlayer()) && p.hasPotionEffect(PotionEffectType.INVISIBILITY)) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - packet.getIntegers().write(0, p.getEntityId()); - WrappedDataWatcher watcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer = WrappedDataWatcher.Registry.get(Byte.class); - watcher.setEntity(p); - watcher.setObject(0, serializer, (byte) (0x20)); // 0x20 set invis - packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects()); + PacketContainer packet = manager.createPacket(PacketType.Play.Server.ENTITY_METADATA); // Create packet + packet.getIntegers().write(0, p.getEntityId()); // Set player to modify + + StructureModifier> watchableAccessor = packet.getDataValueCollectionModifier(); + List values = Lists.newArrayList( + new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte)0x20) // 0x20 sets as invisible + ); + + watchableAccessor.write(0, values); manager.sendServerPacket(click.getPlayer(), packet); } } diff --git a/src/mddev0/mafiacraft/commands/MafiaCraftAdminCMD.java b/src/mddev0/mafiacraft/commands/MafiaCraftAdminCMD.java index 5fed3de..79e69ab 100644 --- a/src/mddev0/mafiacraft/commands/MafiaCraftAdminCMD.java +++ b/src/mddev0/mafiacraft/commands/MafiaCraftAdminCMD.java @@ -195,15 +195,17 @@ public boolean onCommand(CommandSender sender, @SuppressWarnings("NullableProble case MAFIA -> ChatColor.RED; case VILLAGE -> ChatColor.DARK_GREEN; case SOLO -> { - if (p.getRole().toString().equals("Serial Killer")) + if (p.getRole() == Role.SERIAL_KILLER) yield ChatColor.BLUE; - else if (p.getRole().toString().equals("Trapper")) - yield ChatColor.DARK_AQUA; + else if (p.getRole() == Role.WEREWOLF) + yield ChatColor.DARK_RED; else yield ChatColor.DARK_GRAY; // Should never be used } case NONE -> { - if (p.getRole().toString().equals("Jester")) + if (p.getRole() == Role.JESTER) yield ChatColor.LIGHT_PURPLE; + if (p.getRole() == Role.SORCERER) + yield ChatColor.DARK_AQUA; else yield ChatColor.YELLOW; // Will be used } case VAMPIRES -> ChatColor.DARK_PURPLE; @@ -255,15 +257,17 @@ else if (p.getRole().toString().equals("Trapper")) p.teleport(toSpawn); } p.setGameMode(GameMode.SURVIVAL); - // all dead players should be hidden, p should be unhidden - for (Player other : plugin.getServer().getOnlinePlayers()) { - other.showPlayer(plugin, p); - MafiaPlayer spec = plugin.getPlayerList().get(other.getUniqueId()); - if (spec == null || !spec.isLiving()) { - p.hidePlayer(plugin, other); + if (plugin.getConfig().getBoolean("hideDeadPlayers")){ + // all dead players should be hidden, p should be unhidden + for (Player other : plugin.getServer().getOnlinePlayers()) { + other.showPlayer(plugin, p); + MafiaPlayer spec = plugin.getPlayerList().get(other.getUniqueId()); + if (spec == null || !spec.isLiving()) { + p.hidePlayer(plugin, other); + } } + Bukkit.broadcastMessage(ChatColor.YELLOW + p.getName() + " joined the game"); } - Bukkit.broadcastMessage(ChatColor.YELLOW + p.getName() + " joined the game"); sender.sendMessage(ChatColor.GREEN + args[1] + " has been revived."); return true; } diff --git a/src/mddev0/mafiacraft/gui/InfoGUI.java b/src/mddev0/mafiacraft/gui/InfoGUI.java index d46aa60..462b6a3 100644 --- a/src/mddev0/mafiacraft/gui/InfoGUI.java +++ b/src/mddev0/mafiacraft/gui/InfoGUI.java @@ -62,7 +62,12 @@ private void fillInventory() { case MAFIA -> ChatColor.RED; case VILLAGE -> ChatColor.DARK_GREEN; case SOLO -> ChatColor.BLUE; - case NONE -> ChatColor.YELLOW; + case NONE -> + switch (caller.getRole()) { + case JESTER -> ChatColor.LIGHT_PURPLE; + case SORCERER -> ChatColor.DARK_AQUA; + default -> ChatColor.YELLOW; + }; case VAMPIRES -> ChatColor.DARK_PURPLE; } + Objects.requireNonNull(Bukkit.getPlayer(caller.getID())).getName() + " | " + caller.getRole().toString(); @@ -79,10 +84,12 @@ private void fillInventory() { if (caller.getRole() == Role.JESTER) yield "if " + ChatColor.LIGHT_PURPLE + "killed" + ChatColor.GRAY + " by a member of the " + ChatColor.DARK_GREEN + "Village"; + else if (caller.getRole() == Role.SORCERER) + yield ChatColor.GRAY + "by surviving to see " + ChatColor.DARK_AQUA + "your secret allegiance" + ChatColor.GRAY + " win"; else - yield ChatColor.YELLOW + "by surviving"; + yield ChatColor.GRAY + "by completing all " + ChatColor.YELLOW + "tasks and win conditions"; } - case VAMPIRES -> "by " + ChatColor.DARK_AQUA + "Converting all players to Vampires"; + case VAMPIRES -> "by " + ChatColor.DARK_PURPLE + "Converting all players to Vampires"; }); headLore.add(ChatColor.GRAY + "See all your abilities below."); if (caller.getRole().getAlignment() == Role.Team.MAFIA || caller.getRole().getAlignment() == Role.Team.VAMPIRES) { @@ -91,7 +98,11 @@ private void fillInventory() { } else if (caller.getRole() == Role.HUNTER) { headLore.add(ChatColor.GRAY + "The names and status of your"); headLore.add(ChatColor.GRAY + "targets are listed below."); + } else if (caller.getRole() == Role.BODYGUARD) { + headLore.add(ChatColor.GRAY + "The name and status of your"); + headLore.add(ChatColor.GRAY + "protectee is listed below."); } + headLore.add(ChatColor.DARK_GRAY + "See the role descriptions online if you're still confused."); headMeta.setLore(headLore); head.setItemMeta(headMeta); inv.setItem(4,head); @@ -120,22 +131,35 @@ private void fillInventory() { for (Ability abil : caller.getRole().getAbilities()) { ItemStack abilItem; ItemMeta abilMeta; - if (caller.getCooldowns().isOnCooldown(abil)) { // On Cooldown + if (abil == Ability.JUST_A_PRANK && (Boolean)caller.getRoleData().getData(RoleData.DataType.JESTER_ABILITY_USED)) { + // One-time use (this ability only) + abilItem = new ItemStack(Material.GRAY_CONCRETE); + abilMeta = abilItem.getItemMeta(); + assert abilMeta != null; + abilMeta.setDisplayName(ChatColor.DARK_GRAY + abil.toString()); + ArrayList abilLore = new ArrayList<>(); + abilLore.add(ChatColor.RESET + "" + ChatColor.GRAY + "This ability may only be used once."); + abilLore.add(ChatColor.DARK_AQUA + "" + ChatColor.ITALIC + abil.getDesc()); + abilMeta.setLore(abilLore); + abilItem.setItemMeta(abilMeta); + } else if (caller.getCooldowns().isOnCooldown(abil)) { // On Cooldown abilItem = new ItemStack(Material.YELLOW_CONCRETE); abilMeta = abilItem.getItemMeta(); assert abilMeta != null; - abilMeta.setDisplayName(ChatColor.GOLD + abil.fullName()); + abilMeta.setDisplayName(ChatColor.GOLD + abil.toString()); ArrayList abilLore = new ArrayList<>(); abilLore.add(ChatColor.RESET + "" + ChatColor.YELLOW + "This ability is on cooldown."); + abilLore.add(ChatColor.DARK_AQUA + "" + ChatColor.ITALIC + abil.getDesc()); abilMeta.setLore(abilLore); abilItem.setItemMeta(abilMeta); } else { // Not On Cooldown abilItem = new ItemStack(Material.WHITE_CONCRETE); abilMeta = abilItem.getItemMeta(); assert abilMeta != null; - abilMeta.setDisplayName(ChatColor.AQUA + abil.fullName()); + abilMeta.setDisplayName(ChatColor.AQUA + abil.toString()); ArrayList abilLore = new ArrayList<>(); abilLore.add(ChatColor.RESET + "" + ChatColor.WHITE + "This ability is available!"); + abilLore.add(ChatColor.DARK_AQUA + "" + ChatColor.ITALIC + abil.getDesc()); abilMeta.setLore(abilLore); abilItem.setItemMeta(abilMeta); } @@ -145,7 +169,8 @@ private void fillInventory() { abilityNumber++; } - // List of Teammates + // TODO: Switchify + // List of Teammates or other attributes if (caller.getRole().getAlignment() == Role.Team.MAFIA) { // If Mafia int teammateNumber = 0; for (MafiaPlayer other : plugin.getLivingPlayers().values()) { @@ -177,7 +202,7 @@ private void fillInventory() { teamMeta = (SkullMeta) teamHead.getItemMeta(); assert teamMeta != null; teamMeta.setOwningPlayer(Bukkit.getOfflinePlayer(other.getID())); - teamTitle = ChatColor.DARK_AQUA + Bukkit.getOfflinePlayer(other.getID()).getName() + " | " + other.getRole().toString(); + teamTitle = ChatColor.DARK_PURPLE + Bukkit.getOfflinePlayer(other.getID()).getName() + " | " + other.getRole().toString(); teamMeta.setDisplayName(teamTitle); teamHead.setItemMeta(teamMeta); // Set Item @@ -210,6 +235,36 @@ private void fillInventory() { inv.setItem(targetPos, targetHead); targetNumber++; } + } else if (caller.getRole() == Role.BODYGUARD) { + String uuid = (String)caller.getRoleData().getData(RoleData.DataType.BODYGUARD_PROTECTEE); + MafiaPlayer protectee = plugin.getPlayerList().get(UUID.fromString(uuid)); + ItemStack protecteeHead = new ItemStack(Material.PLAYER_HEAD); + SkullMeta protecteeMeta = (SkullMeta) protecteeHead.getItemMeta(); + assert protecteeMeta != null; + protecteeMeta.setOwningPlayer(Bukkit.getOfflinePlayer(UUID.fromString(uuid))); + String protecteeTitle; + if (protectee.isLiving()) + protecteeTitle = ChatColor.AQUA + Bukkit.getOfflinePlayer(UUID.fromString(uuid)).getName() + " | Alive"; + else + protecteeTitle = ChatColor.DARK_GRAY + Bukkit.getOfflinePlayer(UUID.fromString(uuid)).getName() + " | Dead"; + protecteeMeta.setDisplayName(protecteeTitle); + protecteeHead.setItemMeta(protecteeMeta); + inv.setItem(17, protecteeHead); + } else if (caller.getRole() == Role.SORCERER) { + ItemStack indicator; + String indicatorTitle = ChatColor.GRAY + "Secret Alignment: "; + if (Role.Team.valueOf((String)caller.getRoleData().getData(RoleData.DataType.SORCERER_ALIGNMENT)) == Role.Team.VILLAGE) { + indicator = new ItemStack(Material.LIME_WOOL); + indicatorTitle += ChatColor.DARK_GREEN + "Village"; + } else { // aligned to Mafia + indicator = new ItemStack(Material.RED_WOOL); + indicatorTitle += ChatColor.RED + "Mafia"; + } + ItemMeta indicatorMeta = indicator.getItemMeta(); + assert indicatorMeta != null; + indicatorMeta.setDisplayName(indicatorTitle); + indicator.setItemMeta(indicatorMeta); + inv.setItem(17, indicator); } // TODO: Add indicators for Jester and Werewolves } diff --git a/src/mddev0/mafiacraft/gui/ReanimationGUI.java b/src/mddev0/mafiacraft/gui/ReviveGUI.java similarity index 81% rename from src/mddev0/mafiacraft/gui/ReanimationGUI.java rename to src/mddev0/mafiacraft/gui/ReviveGUI.java index 68bc67d..7449945 100644 --- a/src/mddev0/mafiacraft/gui/ReanimationGUI.java +++ b/src/mddev0/mafiacraft/gui/ReviveGUI.java @@ -18,12 +18,12 @@ import java.util.Objects; import java.util.UUID; -public final class ReanimationGUI implements Listener { +public final class ReviveGUI implements Listener { private final MafiaCraft plugin; private final Inventory inv; - public ReanimationGUI(MafiaCraft plugin) { + public ReviveGUI(MafiaCraft plugin) { this.plugin = plugin; Bukkit.getPluginManager().registerEvents(this, plugin); @@ -60,8 +60,8 @@ public void onHeadClick(final InventoryClickEvent click) { if (clicked == null || clicked.getType().isAir()) return; // SCUFFED: So many requireNonNull... am I doing this wrong? UUID toReanimate = Objects.requireNonNull(((SkullMeta) Objects.requireNonNull(clicked.getItemMeta())).getOwningPlayer()).getUniqueId(); - Long cooldownExpires = (plugin.getWorldFullTime() + 48000L) - (plugin.getWorldFullTime() % 24000); - plugin.getLivingPlayers().get(click.getWhoClicked().getUniqueId()).getCooldowns().startCooldown(Ability.REANIMATION, cooldownExpires); + Long cooldownExpires = (plugin.getWorldFullTime() + 864000L); + plugin.getLivingPlayers().get(click.getWhoClicked().getUniqueId()).getCooldowns().startCooldown(Ability.REVIVE, cooldownExpires); plugin.getLivingPlayers().get(click.getWhoClicked().getUniqueId()).getStatus().startStatus(StatusData.Status.UNHOLY, plugin.getWorldFullTime() + 48000L); plugin.getPlayerList().get(toReanimate).makeAlive(); click.getWhoClicked().sendMessage(ChatColor.GREEN + Bukkit.getOfflinePlayer(toReanimate).getName() + " has been revived."); @@ -77,15 +77,17 @@ public void onHeadClick(final InventoryClickEvent click) { p.teleport(toSpawn); } p.setGameMode(GameMode.SURVIVAL); - // all dead players should be hidden, p should be unhidden - for (Player other : plugin.getServer().getOnlinePlayers()) { - other.showPlayer(plugin, p); - MafiaPlayer spec = plugin.getPlayerList().get(other.getUniqueId()); - if (spec == null || !spec.isLiving()) { - p.hidePlayer(plugin, other); + if (plugin.getConfig().getBoolean("hideDeadPlayers")) { + // all dead players should be hidden, p should be unhidden + for (Player other : plugin.getServer().getOnlinePlayers()) { + other.showPlayer(plugin, p); + MafiaPlayer spec = plugin.getPlayerList().get(other.getUniqueId()); + if (spec == null || !spec.isLiving()) { + p.hidePlayer(plugin, other); + } } + Bukkit.broadcastMessage(ChatColor.YELLOW + p.getName() + " joined the game"); } - Bukkit.broadcastMessage(ChatColor.YELLOW + p.getName() + " joined the game"); } } diff --git a/src/mddev0/mafiacraft/player/MafiaPlayer.java b/src/mddev0/mafiacraft/player/MafiaPlayer.java index 48473c3..c14be3e 100644 --- a/src/mddev0/mafiacraft/player/MafiaPlayer.java +++ b/src/mddev0/mafiacraft/player/MafiaPlayer.java @@ -103,12 +103,17 @@ public void changeRole(Role newRole) { // If this role has added attributes, set defaults if needed switch (role) { case JESTER -> roleData.setData(RoleData.DataType.JESTER_ABILITY_USED, false); - case SORCERER -> roleData.setData(RoleData.DataType.SORCERER_SELECTED, Ability.SPELL_BOOK); + case SORCERER -> { + roleData.setData(RoleData.DataType.SORCERER_SELECTED, Ability.SPELL_BOOK); + // Ternary operator to randomly pick if they are Village or Mafia + Role.Team alignment = (new Random().nextInt(2) == 0) ? Role.Team.VILLAGE : Role.Team.MAFIA; + roleData.setData(RoleData.DataType.SORCERER_ALIGNMENT, alignment.name()); + } case WEREWOLF -> { roleData.setData(RoleData.DataType.WEREWOLF_TRANSFORM, false); roleData.setData(RoleData.DataType.WEREWOLF_KILLS, 0); } - case HUNTER -> { + case HUNTER -> { // TODO: Deal with no players in game Set targets = new HashSet<>(); List allLiving = new ArrayList<>(plugin.getLivingPlayers().keySet().stream().toList()); allLiving.remove(uuid); @@ -117,6 +122,11 @@ public void changeRole(Role newRole) { targets.add(allLiving.remove(new Random().nextInt(allLiving.size())).toString()); // SCUFFED: oops terrible practice roleData.setData(RoleData.DataType.HUNTER_TARGETS, targets); } + case BODYGUARD -> { + List allLiving = new ArrayList<>(plugin.getLivingPlayers().keySet().stream().toList()); + allLiving.remove(uuid); + roleData.setData(RoleData.DataType.BODYGUARD_PROTECTEE, allLiving.get(new Random().nextInt(allLiving.size())).toString()); + } } diff --git a/src/mddev0/mafiacraft/player/Role.java b/src/mddev0/mafiacraft/player/Role.java index af7c281..554698f 100644 --- a/src/mddev0/mafiacraft/player/Role.java +++ b/src/mddev0/mafiacraft/player/Role.java @@ -8,23 +8,23 @@ public enum Role { GODFATHER("Godfather", true, Team.MAFIA, Ability.PROTECTION, Ability.AMBUSH), MAFIOSO("Mafioso", false, Team.MAFIA, Ability.SUCCESSION), FRAMER("Framer", false, Team.MAFIA, Ability.FORGERY), - ASSASSIN("Assassin", false, Team.MAFIA, Ability.ASSASSINATION), + ASSASSIN("Assassin", false, Team.MAFIA, Ability.ASSASSINATE), // TODO: BREWER("Brewer", false, Team.MAFIA, ???), - REANIMATOR("Reanimator", true, Team.VILLAGE, Ability.REANIMATION), - VETERAN("Veteran", false, Team.VILLAGE, Ability.RETALIATION), + REANIMATOR("Reanimator", true, Team.VILLAGE, Ability.REVIVE), + VETERAN("Veteran", false, Team.VILLAGE, Ability.RETALIATE), DEPUTY("Deputy", false, Team.VILLAGE, Ability.HIGH_NOON, Ability.MARKSMAN), INVESTIGATOR("Investigator", false, Team.VILLAGE, Ability.INVESTIGATE), LOOKOUT("Lookout", false, Team.VILLAGE, Ability.WATCH, Ability.PERIPHERALS, Ability.CLEAR_SIGHT), DOCTOR("Doctor", false, Team.VILLAGE, Ability.RESCUE), APOTHECARY("Apothecary", true, Team.VILLAGE, Ability.AMBROSIA), - DEACON("Deacon", false, Team.VILLAGE, Ability.INQUISITION), + PRIEST("Priest", false, Team.VILLAGE, Ability.INQUISITION), SERIAL_KILLER("Serial Killer", false, Team.SOLO, Ability.PROTECTION, Ability.AMBUSH), - TRAPPER("Trapper", false, Team.SOLO, Ability.THIS_IS_FINE, Ability.DODGE_ROLL), + BODYGUARD("Bodyguard", false, Team.NONE, Ability.PROTECTEE, Ability.THIS_IS_FINE, Ability.DODGE_ROLL, Ability.TRACKING), HUNTER("Hunter", false, Team.NONE, Ability.TARGET, Ability.TRACKING), - SORCERER("Sorcerer", false, Team.NONE, Ability.SCATTER, Ability.TOADIFY, Ability.FOG_OF_WAR, Ability.VANISH, Ability.SPELL_BOOK), - WEREWOLF("Werewolf", false, Team.NONE, Ability.TRANSFORM, Ability.RAMPAGE, Ability.NEMESIS), + SORCERER("Sorcerer", true, Team.NONE, Ability.SCATTER, Ability.TOADIFY, Ability.FOG_OF_WAR, Ability.VANISH, Ability.SPELL_BOOK, Ability.SHADOW_PLEDGE), + WEREWOLF("Werewolf", false, Team.SOLO, Ability.TRANSFORM, Ability.RAMPAGE, Ability.NEMESIS), VAMPIRE("Vampire", true, Team.VAMPIRES, Ability.CONVERT, Ability.NIGHT_OWL, Ability.STAKED), - JESTER("Jester", true, Team.NONE, Ability.JUST_A_PRANK); + JESTER("Jester", false, Team.NONE, Ability.JUST_A_PRANK); private final String NAME; private final boolean UNIQUE; diff --git a/src/mddev0/mafiacraft/player/RoleData.java b/src/mddev0/mafiacraft/player/RoleData.java index 90bdd9b..07ae71f 100644 --- a/src/mddev0/mafiacraft/player/RoleData.java +++ b/src/mddev0/mafiacraft/player/RoleData.java @@ -44,8 +44,10 @@ public enum DataType { WEREWOLF_TRANSFORM(Boolean.class), WEREWOLF_KILLS(Integer.class), SORCERER_SELECTED(Ability.class), + SORCERER_ALIGNMENT(String.class), JESTER_ABILITY_USED(Boolean.class), - HUNTER_TARGETS(HashSet.class); + HUNTER_TARGETS(HashSet.class), + BODYGUARD_PROTECTEE(String.class); private final Type TYPENAME; DataType(Type typename) { diff --git a/src/mddev0/mafiacraft/util/DeathManager.java b/src/mddev0/mafiacraft/util/DeathManager.java index c019fa5..a4cea6e 100644 --- a/src/mddev0/mafiacraft/util/DeathManager.java +++ b/src/mddev0/mafiacraft/util/DeathManager.java @@ -40,17 +40,25 @@ public void onPlayerDeathLate(PlayerDeathEvent death) { Bukkit.getLogger().log(Level.INFO, death.getDeathMessage() + " <" + death.getEntity().getWorld().getName() + ": " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ">"); } if (!plugin.getActive()) return; - MafiaPlayer dead = plugin.getPlayerList().get(death.getEntity().getUniqueId()); - if (dead != null && !dead.isLiving() && death.getEntity().getGameMode() != GameMode.SPECTATOR) { - Player died = death.getEntity(); - Bukkit.broadcastMessage(ChatColor.YELLOW + died.getName() + " left the game"); - for (Player p : plugin.getServer().getOnlinePlayers()) { - MafiaPlayer live = plugin.getLivingPlayers().get(p.getUniqueId()); - if (live != null) { - // player is online and alive - p.hidePlayer(plugin, died); + if (plugin.getConfig().getBoolean("hideDeadPlayers")) { + // Execute only if hiding dead players + MafiaPlayer dead = plugin.getPlayerList().get(death.getEntity().getUniqueId()); + if (dead != null && !dead.isLiving() && death.getEntity().getGameMode() != GameMode.SPECTATOR) { + Player died = death.getEntity(); + Bukkit.broadcastMessage(ChatColor.YELLOW + died.getName() + " left the game"); + for (Player p : plugin.getServer().getOnlinePlayers()) { + MafiaPlayer live = plugin.getLivingPlayers().get(p.getUniqueId()); + if (live != null) { + // player is online and alive + p.hidePlayer(plugin, died); + } } } + } else { + // If not hiding dead players, announce their death BUT NOT THE CAUSE + Location loc = death.getEntity().getLocation(); + Bukkit.getLogger().log(Level.INFO, death.getDeathMessage() + " <" + death.getEntity().getWorld().getName() + ": " + loc.getBlockX() + ", " + loc.getBlockY() + ", " + loc.getBlockZ() + ">"); + death.setDeathMessage(death.getEntity().getDisplayName() + ChatColor.RESET + " died"); } } diff --git a/src/mddev0/mafiacraft/util/GameFinisher.java b/src/mddev0/mafiacraft/util/GameFinisher.java index 9b6f1b9..88199bb 100644 --- a/src/mddev0/mafiacraft/util/GameFinisher.java +++ b/src/mddev0/mafiacraft/util/GameFinisher.java @@ -42,19 +42,18 @@ else if (dayTime > 12786 && dayTime <= 13000 && !checkedTonight) { // Should check if the game should end by counting each win condition // Count remaining players - int mafiaRemaining, villageRemaining, soloRemaining, vampRemaining, freeRemaining; - mafiaRemaining = villageRemaining = soloRemaining = vampRemaining = freeRemaining = 0; + int mafiaRemaining, villageRemaining, soloRemaining, vampRemaining; + mafiaRemaining = villageRemaining = soloRemaining = vampRemaining = 0; for (MafiaPlayer mp : plugin.getLivingPlayers().values()) { switch (mp.getRole().getAlignment()) { case VILLAGE -> villageRemaining++; case MAFIA -> mafiaRemaining++; case SOLO -> soloRemaining++; case VAMPIRES -> vampRemaining++; - case NONE -> freeRemaining++; } } - // If there are no opposing groups remaining + // If there are no opposing groups remaining the game might be ready to end if (mafiaRemaining + villageRemaining + soloRemaining + vampRemaining == Math.max(Math.max(mafiaRemaining, villageRemaining), Math.max(soloRemaining, vampRemaining))) { // if that super complicated expression clears then the game might be ready to end // Essentially checks if sum of all groups alive is equal to the number of the largest group alive (only true if all but one of them are 0) @@ -85,8 +84,8 @@ else if (dayTime > 12786 && dayTime <= 13000 && !checkedTonight) { winnerSet.add(winner); if (winner.getRole() == Role.SERIAL_KILLER) subtitle = ChatColor.GRAY + "The " + ChatColor.BLUE + ChatColor.BOLD + "Serial Killer" + ChatColor.RESET + ChatColor.GRAY + " has won!"; - else if (winner.getRole() == Role.TRAPPER) - subtitle = ChatColor.GRAY + "The " + ChatColor.DARK_AQUA + ChatColor.BOLD + "Trapper" + ChatColor.RESET + ChatColor.GRAY + " has won!"; + else if (winner.getRole() == Role.WEREWOLF) + subtitle = ChatColor.GRAY + "The " + ChatColor.DARK_RED + ChatColor.BOLD + "Werewolf" + ChatColor.RESET + ChatColor.GRAY + " has won!"; break; // since we only take 1 } } @@ -99,16 +98,18 @@ else if (winner.getRole() == Role.TRAPPER) } subtitle = ChatColor.GRAY + "The " + ChatColor.DARK_PURPLE + ChatColor.BOLD + "Vampires" + ChatColor.RESET + ChatColor.GRAY + " have won!"; } - } else if (mafiaRemaining + villageRemaining + soloRemaining + vampRemaining == 0) { // only survivors left somehow + } else if (mafiaRemaining + villageRemaining + soloRemaining + vampRemaining == 0 && !plugin.getLivingPlayers().isEmpty()) { // only unaligned left somehow endGame = true; - subtitle = ChatColor.GRAY + "The " + ChatColor.YELLOW + ChatColor.BOLD + "Survivors" + ChatColor.RESET + ChatColor.GRAY + " have won!"; + subtitle = ChatColor.GRAY + "Only " + ChatColor.YELLOW + ChatColor.BOLD + "Neutral" + ChatColor.RESET + ChatColor.GRAY + " players are alive!"; + } else if (plugin.getLivingPlayers().isEmpty()) { + endGame = true; + subtitle = ChatColor.GRAY + "Everyone died!"; } - // Survivor roles - if (freeRemaining > 0) { - // Now check if any survivor roles have reached their win conditions and are alive - for (MafiaPlayer mp : plugin.getLivingPlayers().values()) { // survivors ONLY win if alive - if (mp.getRole() == Role.HUNTER) { + // Unaligned roles + for (MafiaPlayer mp : plugin.getPlayerList().values()) { + switch (mp.getRole()) { + case HUNTER -> { // Check if targets are killed boolean hunterWin = true; // SCUFFED: Yes, this next line is an unchecked cast. Guess I just have to be careful. @@ -116,10 +117,25 @@ else if (winner.getRole() == Role.TRAPPER) hunterWin = hunterWin && !plugin.getPlayerList().get(UUID.fromString(id)).isLiving(); } if (hunterWin) winnerSet.add(mp); - } else if (mp.getRole() == Role.JESTER && (Boolean)mp.getRoleData().getData(RoleData.DataType.JESTER_ABILITY_USED)) { - winnerSet.add(mp); - } else { // Other surviving roles have no special win conditions - winnerSet.add(mp); + } + case JESTER -> { + if ((Boolean)mp.getRoleData().getData(RoleData.DataType.JESTER_ABILITY_USED)) + winnerSet.add(mp); + } + case BODYGUARD -> { + if (plugin.getLivingPlayers().containsKey(UUID.fromString((String)mp.getRoleData().getData(RoleData.DataType.BODYGUARD_PROTECTEE)))) + winnerSet.add(mp); + } + case SORCERER -> { + if (Role.Team.valueOf((String) mp.getRoleData().getData(RoleData.DataType.SORCERER_ALIGNMENT)) == Role.Team.VILLAGE) { + if (villageRemaining > 0) winnerSet.add(mp); + } + else { // they're aligned to Mafia + if (mafiaRemaining > 0) winnerSet.add(mp); + } + } + default -> { + if (mp.isLiving()) winnerSet.add(mp); } } } @@ -136,7 +152,10 @@ else if (winner.getRole() == Role.TRAPPER) } // Winner list String winnersMessage = ChatColor.GREEN + ""; - if (winners.size() == 1) { + if (winners.isEmpty()) { + winnersMessage = winnersMessage.concat("Nobody wins!"); + } + else if (winners.size() == 1) { winnersMessage = winnersMessage.concat(Bukkit.getOfflinePlayer(winners.get(0).getID()).getName() + " is the winner!"); } else if (winners.size() == 2) { winnersMessage = winnersMessage.concat(Bukkit.getOfflinePlayer(winners.get(0).getID()).getName() + " and " + @@ -148,6 +167,7 @@ else if (winner.getRole() == Role.TRAPPER) winnersMessage = winnersMessage.concat(" and " + winners.get(winners.size()-1) + " are the winners!"); } Bukkit.broadcastMessage(winnersMessage); + // List of all players List playerList = new ArrayList<>(); String playerListHeader = ChatColor.GOLD + "All of the players who played in this game:"; @@ -168,11 +188,12 @@ else if (winner.getRole() == Role.TRAPPER) case VILLAGE -> ChatColor.DARK_GREEN; case SOLO-> { if (p.getRole() == Role.SERIAL_KILLER) yield ChatColor.BLUE; - else if (p.getRole() == Role.TRAPPER) yield ChatColor.DARK_AQUA; + else if (p.getRole() == Role.WEREWOLF) yield ChatColor.DARK_RED; else yield ChatColor.DARK_GRAY; // Should never be used } case NONE -> { if (p.getRole() == Role.JESTER) yield ChatColor.LIGHT_PURPLE; + if (p.getRole() == Role.SORCERER) yield ChatColor.DARK_AQUA; else yield ChatColor.YELLOW; // Will be used } case VAMPIRES -> { @@ -188,7 +209,7 @@ else if (winner.getRole() == Role.TRAPPER) case VILLAGE -> ChatColor.DARK_GREEN; case SOLO -> { if (p.getOriginalRole() == Role.SERIAL_KILLER) yield ChatColor.BLUE; - else if (p.getOriginalRole() == Role.TRAPPER) yield ChatColor.DARK_AQUA; + else if (p.getOriginalRole() == Role.WEREWOLF) yield ChatColor.DARK_RED; else yield ChatColor.DARK_GRAY; // Should never be used } case NONE -> { diff --git a/src/mddev0/mafiacraft/util/GameRandomizer.java b/src/mddev0/mafiacraft/util/GameRandomizer.java index 13ca73a..cd3802a 100644 --- a/src/mddev0/mafiacraft/util/GameRandomizer.java +++ b/src/mddev0/mafiacraft/util/GameRandomizer.java @@ -1,6 +1,7 @@ package mddev0.mafiacraft.util; import mddev0.mafiacraft.MafiaCraft; +import mddev0.mafiacraft.abilities.Ability; import mddev0.mafiacraft.player.MafiaPlayer; import mddev0.mafiacraft.player.Role; import mddev0.mafiacraft.player.RoleData; @@ -137,23 +138,29 @@ else if (!bannedRoles.contains(r.name())) // This loop could go on for ages... oh well // and by ages I mean worst case is potentially infinite final Random rand = new Random(); + int consecutiveFailures = 0; while (!playersToRoll.isEmpty()) { + if (consecutiveFailures > 100) + throw new RandomizationException("Stopping randomization due to possible infinite loop. This may be a fluke, " + + "or may be because there are more required roles of one type than the role ratios can fill."); + // Get random player OfflinePlayer player = playersToRoll.get(rand.nextInt(playersToRoll.size())); Role role = null; // this will store the assigned role // Restock roles if there are none left - for (Role r : Role.values()) - if (!r.isUnique() && !bannedRoles.contains(r.name())) // Role is not unique/banned - availableRoles.add(r); + if (availableRoles.isEmpty()) { + for (Role r : Role.values()) + if (!r.isUnique() && !bannedRoles.contains(r.name())) // Role is not unique/banned + availableRoles.add(r); + } // If there are required roles outstanding, assign them first if (!requiredRoles.isEmpty()) do { try { role = Role.valueOf(requiredRoles.get(0)); - } catch (IllegalArgumentException ex) { Bukkit.getLogger().log(Level.WARNING, "Skipping an invalid entry in the list of required roles: " + requiredRoles.remove(0)); } @@ -166,20 +173,29 @@ else if (!bannedRoles.contains(r.name())) if (numMafia > 0 || numVillage > 0 || numNeutral > 0) { switch (role.getAlignment()) { case MAFIA -> { - if (numMafia <= 0) // Finish others before adding more mafia + if (numMafia <= 0) { // Finish others before adding more mafia + availableRoles.remove(role); + consecutiveFailures++; continue; // Just skip this iteration :) + } else // keep going, and update number of mafia numMafia--; } case VILLAGE -> { - if (numVillage <= 0) // Finish others before adding more villagers + if (numVillage <= 0) { // Finish others before adding more villagers + availableRoles.remove(role); + consecutiveFailures++; continue; // Just skip this iteration :) + } else // keep going, and update number of villagers numVillage--; } default -> { - if (numNeutral <= 0) // Finish others before adding more neutrals + if (numNeutral <= 0) { // Finish others before adding more neutrals + availableRoles.remove(role); + consecutiveFailures++; continue; // Just skip this iteration :) + } else // keep going, and update number of neutrals numNeutral--; } @@ -187,6 +203,7 @@ else if (!bannedRoles.contains(r.name())) } // at this point we know the player can be added to the role + consecutiveFailures = 0; // Prevent reselect of role requiredRoles.remove(role.name()); availableRoles.remove(role); @@ -198,16 +215,16 @@ else if (!bannedRoles.contains(r.name())) plugin.getPlayerList().put(mafiaPlayer.getID(), mafiaPlayer); if (plugin.getPlayerList().containsKey(mafiaPlayer.getID())){ playersToRoll.remove(player); - // I think the logger is thread safe Bukkit.getLogger().log(Level.INFO, "[MafiaCraft] Set random role for player " + player.getName() + " (" + player.getUniqueId() + ")"); } else { Bukkit.getLogger().log(Level.WARNING, "[MafiaCraft] Could not add " + player.getName() + " (" + player.getUniqueId() + ") to the MafiaCraft player list"); } } - // All players should have been set up by this point do hunter targets + // All players should have been set up by this point, do hunter targets + // FIXME: If all roles are manually assigned, this process will never happen. for (MafiaPlayer mp : plugin.getPlayerList().values()) { - if (mp.getRole() == Role.HUNTER) { + if (mp.getRole().getAbilities().contains(Ability.TARGET)) { Set targets = new HashSet<>(); List allLiving = new ArrayList<>(plugin.getLivingPlayers().keySet().stream().toList()); allLiving.remove(mp.getID()); @@ -216,6 +233,16 @@ else if (!bannedRoles.contains(r.name())) targets.add(allLiving.remove(rand.nextInt(allLiving.size())).toString()); mp.getRoleData().setData(RoleData.DataType.HUNTER_TARGETS, targets); } + if (mp.getRole().getAbilities().contains(Ability.PROTECTEE)) { + List allLiving = new ArrayList<>(plugin.getLivingPlayers().keySet().stream().toList()); + allLiving.remove(mp.getID()); + String protectee = allLiving.get(rand.nextInt(allLiving.size())).toString(); + mp.getRoleData().setData(RoleData.DataType.BODYGUARD_PROTECTEE, protectee); + } if (mp.getRole().getAbilities().contains(Ability.SHADOW_PLEDGE)) { + // Ternary operator to randomly pick if they are Village or Mafia + Role.Team alignment = (new Random().nextInt(2) == 0) ? Role.Team.VILLAGE : Role.Team.MAFIA; + mp.getRoleData().setData(RoleData.DataType.SORCERER_ALIGNMENT, alignment.name()); + } } } diff --git a/src/mddev0/mafiacraft/util/GameSaver.java b/src/mddev0/mafiacraft/util/GameSaver.java index 6d949f3..0586ce3 100644 --- a/src/mddev0/mafiacraft/util/GameSaver.java +++ b/src/mddev0/mafiacraft/util/GameSaver.java @@ -74,14 +74,20 @@ public static void saveGame() { for (Map.Entry roleDataEntry : playerData.roleData().dataMap().entrySet()) if (roleDataEntry.getKey() == RoleData.DataType.HUNTER_TARGETS) { // Treat this as a collection data.set("roleData." + roleDataEntry.getKey().name(), ((Collection)roleDataEntry.getValue()).stream().toList()); - } else + } else if (roleDataEntry.getKey() == RoleData.DataType.SORCERER_SELECTED) { + data.set("roleData." + roleDataEntry.getKey().name(), ((Ability)roleDataEntry.getValue()).name()); + } else { data.set("roleData." + roleDataEntry.getKey().name(), roleDataEntry.getValue()); + } data.set("originalRole", playerData.originalRole().name()); for (Map.Entry originalRoleDataEntry : playerData.roleData().dataMap().entrySet()) if (originalRoleDataEntry.getKey() == RoleData.DataType.HUNTER_TARGETS) { // Treat this as a collection data.set("originalRoleData." + originalRoleDataEntry.getKey().name(), ((Collection)originalRoleDataEntry.getValue()).stream().toList()); - } else + } else if (originalRoleDataEntry.getKey() == RoleData.DataType.SORCERER_SELECTED) { + data.set("originalRoleData." + originalRoleDataEntry.getKey().name(), ((Ability)originalRoleDataEntry.getValue()).name()); + } else { data.set("originalRoleData." + originalRoleDataEntry.getKey().name(), originalRoleDataEntry.getValue()); + } // Cooldown Data for (Map.Entry cooldownDataEntry : playerData.cooldowns().cooldownMap().entrySet()) data.set("cooldowns." + cooldownDataEntry.getKey().name(), cooldownDataEntry.getValue()); @@ -150,10 +156,12 @@ public static void loadGame() { if (RoleData.DataType.valueOf(rd) == RoleData.DataType.HUNTER_TARGETS) { // SCUFFED: this is crude but it'll work List targets = roleDataSection.getList(RoleData.DataType.HUNTER_TARGETS.name()); if (targets == null) { - Bukkit.getLogger().log(Level.SEVERE, "NOPE, IT'S BROKEN"); + Bukkit.getLogger().log(Level.SEVERE, "FAILED TO GET HUNTER TARGETS FROM " + dataFile.getName()); continue; } roleDataMap.put(RoleData.DataType.HUNTER_TARGETS, new HashSet<>(targets)); + } else if (RoleData.DataType.valueOf(rd) == RoleData.DataType.SORCERER_SELECTED) { + roleDataMap.put(RoleData.DataType.SORCERER_SELECTED, Ability.valueOf(roleDataSection.getString(RoleData.DataType.SORCERER_SELECTED.name()))); } else { roleDataMap.put(RoleData.DataType.valueOf(rd), roleDataSection.get(rd)); } @@ -168,7 +176,14 @@ public static void loadGame() { Set rdEntries = originalRoleDataSection.getKeys(false); for (String rd : rdEntries) { if (RoleData.DataType.valueOf(rd) == RoleData.DataType.HUNTER_TARGETS) { // SCUFFED: this is crude but it'll work - originalRoleDataMap.put(RoleData.DataType.HUNTER_TARGETS, new HashSet<>(originalRoleDataSection.getList(RoleData.DataType.HUNTER_TARGETS.name()))); + List targets = originalRoleDataSection.getList(RoleData.DataType.HUNTER_TARGETS.name()); + if (targets == null) { + Bukkit.getLogger().log(Level.SEVERE, "FAILED TO GET HUNTER TARGETS FROM " + dataFile.getName()); + continue; + } + originalRoleDataMap.put(RoleData.DataType.HUNTER_TARGETS, new HashSet<>(targets)); + } else if (RoleData.DataType.valueOf(rd) == RoleData.DataType.SORCERER_SELECTED) { + originalRoleDataMap.put(RoleData.DataType.SORCERER_SELECTED, Ability.valueOf(originalRoleDataSection.getString(RoleData.DataType.SORCERER_SELECTED.name()))); } else { originalRoleDataMap.put(RoleData.DataType.valueOf(rd), originalRoleDataSection.get(rd)); } diff --git a/src/mddev0/mafiacraft/util/JoinLeaveManager.java b/src/mddev0/mafiacraft/util/JoinLeaveManager.java index 4b2ee80..7732f44 100644 --- a/src/mddev0/mafiacraft/util/JoinLeaveManager.java +++ b/src/mddev0/mafiacraft/util/JoinLeaveManager.java @@ -2,10 +2,8 @@ import mddev0.mafiacraft.MafiaCraft; import mddev0.mafiacraft.player.MafiaPlayer; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; +import mddev0.mafiacraft.player.RoleData; +import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -35,37 +33,50 @@ public void onPlayerJoin(PlayerJoinEvent join) { join.getPlayer().teleport(toSpawn); } join.getPlayer().setGameMode(GameMode.SURVIVAL); - // all dead players should be hidden - for (Player p : plugin.getServer().getOnlinePlayers()) { - MafiaPlayer spec = plugin.getPlayerList().get(p.getUniqueId()); - if (spec == null || !spec.isLiving()) { - join.getPlayer().hidePlayer(plugin, p); + if (plugin.getConfig().getBoolean("hideDeadPlayers")) { + // if true, dead players should be hidden + for (Player p : plugin.getServer().getOnlinePlayers()) { + MafiaPlayer spec = plugin.getPlayerList().get(p.getUniqueId()); + if (spec == null || !spec.isLiving()) { + join.getPlayer().hidePlayer(plugin, p); + } } } - } - else { // player is not in game or is dead - // set spectator and hide join message + } else { // player is not in game or is dead + // TODO: set spectator? Potential for dead players to remain in world? join.getPlayer().setGameMode(GameMode.SPECTATOR); - join.setJoinMessage(null); - // hide player from all living players - for (Map.Entry living : plugin.getLivingPlayers().entrySet()) { - OfflinePlayer offp = Bukkit.getOfflinePlayer(living.getKey()); - if (offp.isOnline()) { - Player p = offp.getPlayer(); - assert p != null; - p.hidePlayer(plugin, join.getPlayer()); + if (plugin.getConfig().getBoolean("hideDeadPlayers")) { + // if true, hide player from all living players + join.setJoinMessage(null); + for (Map.Entry living : plugin.getLivingPlayers().entrySet()) { + OfflinePlayer offp = Bukkit.getOfflinePlayer(living.getKey()); + if (offp.isOnline()) { + Player p = offp.getPlayer(); + assert p != null; + p.hidePlayer(plugin, join.getPlayer()); + } } } } + // Handle Jester + if (joined != null) { + Boolean jestStatus = (Boolean) joined.getRoleData().getData(RoleData.DataType.JESTER_ABILITY_USED); + if (jestStatus != null && jestStatus) { + join.getPlayer().setDisplayName("[" + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.RESET + "] " + join.getPlayer().getDisplayName()); + join.getPlayer().setPlayerListName("[" + ChatColor.LIGHT_PURPLE + "Jester" + ChatColor.RESET + "] " + join.getPlayer().getPlayerListName()); + } + } } @SuppressWarnings("unused") @EventHandler public void onPlayerLeave(PlayerQuitEvent leave) { if (!plugin.getActive()) return; - MafiaPlayer left = plugin.getPlayerList().get(leave.getPlayer().getUniqueId()); - if (left == null || !left.isLiving()) { - leave.setQuitMessage(""); + if (plugin.getConfig().getBoolean("hideDeadPlayers")) { + MafiaPlayer left = plugin.getPlayerList().get(leave.getPlayer().getUniqueId()); + if (left == null || !left.isLiving()) { + leave.setQuitMessage(null); + } } } } diff --git a/src/mddev0/mafiacraft/util/SpyglassUtil.java b/src/mddev0/mafiacraft/util/SpyglassUtil.java index 040ad96..cc9b62f 100644 --- a/src/mddev0/mafiacraft/util/SpyglassUtil.java +++ b/src/mddev0/mafiacraft/util/SpyglassUtil.java @@ -31,6 +31,7 @@ public SpyglassUtil(MafiaCraft plugin, @Nonnull OfflinePlayer p) { targeted = null; } + // TODO: Update to use World.rayTraceEntities() @Override public void run() { if (!holder.isOnline()) return; // holder is offline @@ -55,7 +56,14 @@ public void run() { if (active) { // If line of sight between player and target is interrupted, then false. otherwise true double lineDistance = holder.getPlayer().getEyeLocation().distance(targeted.getEyeLocation()); - RayTraceResult lineOfSight = holder.getPlayer().rayTraceBlocks(lineDistance, FluidCollisionMode.NEVER); + Player play = holder.getPlayer(); + RayTraceResult lineOfSight = play.getWorld().rayTraceBlocks( + play.getLocation(), + play.getEyeLocation().toVector().subtract(targeted.getEyeLocation().toVector()).normalize(), + lineDistance, + FluidCollisionMode.NEVER, + true + ); active = (lineOfSight == null); } }