diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..7bc5657 --- /dev/null +++ b/commands.go @@ -0,0 +1,96 @@ +package wrapper + +// Reach objective: verify minecraft version (Bedrock vs. Java and 1.XX) to not build / prevent using certain commands + +type Command interface { + Command() string + Events() []Event +} + +// Could be a great spot to use the github.com/densestvoid/postoffice package. +// It was designed to be able to send and receive on channels identified by interface addresses. +// Each event type could be registered as an address +// Event is for any console resposne, error is for command processing only +func (w *Wrapper) ExecuteCommand(cmd Command) (Event, error) { + // TODO: create/get channels for each event type on the wrapper + + // TODO: write the command to the console + + // TODO: wait to receive on one of the event channels, and return that event + + return nil, nil +} + +/* +attribute +advancement +ban x +ban-ip +banlist x +bossbar +clear +clone +data (get) +datapack +debug +defaultgamemode x +deop x +difficulty x +effect +enchant +execute +experience (add,query) +fill +forceload +function +gamemode +gamerule +give x +help +kick x +kill +list x +locate +locatebiome +loot +me +msg +op +pardon +particle +playsound +publish +recipe +reload +save-all x +save-off x +save-on x +say x +schedule +scoreboard +seed +setblock +setidletimeout +setworldspawn +spawnpoint +spectate +spreadplayers +stop x +stopsound +summon +tag +team +teammsg +teleport +tell x +tellraw +time +title +tp +trigger +w +weather +whitelist +worldborder +xp +*/ diff --git a/commands/java/1.15.2/gamerule.go b/commands/java/1.15.2/gamerule.go new file mode 100644 index 0000000..eb185c1 --- /dev/null +++ b/commands/java/1.15.2/gamerule.go @@ -0,0 +1,135 @@ +package minecraft_1_15 + +import ( + "fmt" + + wrapper "github.com/wlwanpan/minecraft-wrapper" +) + +type gameRuleBoolName string + +func (gr gameRuleBoolName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use booleans +const ( + AnnounceAdvancements gameRuleBoolName = "announceAdvancements" + CommandBlockOutput gameRuleBoolName = "commandBlockOutput" + DisableElytraMovementCheck gameRuleBoolName = "disableElytraMovementCheck" + DisableRaids gameRuleBoolName = "disableRaids" + DoDaylightCycle gameRuleBoolName = "doDaylightCycle" + DoEntityDrops gameRuleBoolName = "doEntityDrops" + DoFireTick gameRuleBoolName = "doFireTick" + DoInsomnia gameRuleBoolName = "doInsomnia" + DoImmediateRespawn gameRuleBoolName = "doImmediateRespawn" + DoLimitedCrafting gameRuleBoolName = "doLimitedCrafting" + DoMobLoot gameRuleBoolName = "doMobLoot" + DoMobSpawning gameRuleBoolName = "doMobSpawning" + DoPatrolSpawning gameRuleBoolName = "doPatrolSpawning" // new + DoTileDrops gameRuleBoolName = "doTileDrops" + DoTraderSpawning gameRuleBoolName = "doTraderSpawning" // new + DoWeatherCycle gameRuleBoolName = "doWeatherCycle" + DrowningDamage gameRuleBoolName = "drowningDamage" + FallDamage gameRuleBoolName = "fallDamage" + FireDamage gameRuleBoolName = "fireDamage" + KeepInventory gameRuleBoolName = "keepInventory" + LogAdminCommands gameRuleBoolName = "logAdminCommands" + MobGriefing gameRuleBoolName = "mobGriefing" + NaturalRegeneration gameRuleBoolName = "naturalRegeneration" + ReducedDebugInfo gameRuleBoolName = "reducedDebugInfo" + SendCommandFeedback gameRuleBoolName = "sendCommandFeedback" + ShowDeathMessages gameRuleBoolName = "showDeathMessages" + SpectatorsGenerateChunks gameRuleBoolName = "spectatorsGenerateChunks" +) + +type gameRuleIntName string + +func (gr gameRuleIntName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use integers +const ( + MaxCommandChainLength gameRuleIntName = "maxCommandChainLength" + MaxEntityCramming gameRuleIntName = "maxEntityCramming" + RandomTickSpeed gameRuleIntName = "randomTickSpeed" + SpawnRadius gameRuleIntName = "spawnRadius" +) + +// GameRuleName accepts either boolean or integer name types +type GameRuleName interface { + gameRuleName() string +} + +// GameRule is a command used to set various rules in game +type GameRule struct { + name GameRuleName + bVal bool + iVal int +} + +func NewGameRuleGet(name GameRuleName) GameRule { + return GameRule{name: name} +} + +func NewGameRuleBoolean(name GameRuleName, b bool) GameRule { + return GameRule{name: name, bVal: b} +} + +func NewGameRuleInt(name GameRuleName, i int) GameRule { + return GameRule{name: name, iVal: i} +} + +// Command allows the GameRule struct to be executed as a command in game +func (c GameRule) Command() string { + switch c.name.(type) { + case gameRuleBoolName: + return fmt.Sprintf("gamerule %s %t", c.name.gameRuleName(), c.bVal) + case gameRuleIntName: + return fmt.Sprintf("gamerule %s %d", c.name.gameRuleName(), c.iVal) + default: + return fmt.Sprintf("gamerule %s", c.name.gameRuleName()) + } +} + +func (c GameRule) Events() []wrapper.Event { + return []wrapper.Event{ + &GameRuleSet{}, + &GameRuleGet{}, + &wrapper.IncorrectCommandArgument{}, + &wrapper.InvalidBoolean{}, + &wrapper.InvalidInteger{}, + &wrapper.UnknownOrIncompleteCommand{}, + } +} + +type GameRuleSet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleSet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %d", event.iVal); err != nil { + return false + } + } + return true +} + +type GameRuleGet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleGet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %d", event.iVal); err != nil { + return false + } + } + return true +} diff --git a/commands/java/1.15/gamerule.go b/commands/java/1.15/gamerule.go new file mode 100644 index 0000000..e204eb6 --- /dev/null +++ b/commands/java/1.15/gamerule.go @@ -0,0 +1,133 @@ +package minecraft_1_15_2 + +import ( + "fmt" + + wrapper "github.com/wlwanpan/minecraft-wrapper" +) + +type gameRuleBoolName string + +func (gr gameRuleBoolName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use booleans +const ( + AnnounceAdvancements gameRuleBoolName = "announceAdvancements" + CommandBlockOutput gameRuleBoolName = "commandBlockOutput" + DisableElytraMovementCheck gameRuleBoolName = "disableElytraMovementCheck" + DisableRaids gameRuleBoolName = "disableRaids" + DoDaylightCycle gameRuleBoolName = "doDaylightCycle" + DoEntityDrops gameRuleBoolName = "doEntityDrops" + DoFireTick gameRuleBoolName = "doFireTick" + DoInsomnia gameRuleBoolName = "doInsomnia" // new + DoImmediateRespawn gameRuleBoolName = "doImmediateRespawn" // new + DoLimitedCrafting gameRuleBoolName = "doLimitedCrafting" + DoMobLoot gameRuleBoolName = "doMobLoot" + DoMobSpawning gameRuleBoolName = "doMobSpawning" + DoTileDrops gameRuleBoolName = "doTileDrops" + DoWeatherCycle gameRuleBoolName = "doWeatherCycle" + DrowningDamage gameRuleBoolName = "drowningDamage" // new + FallDamage gameRuleBoolName = "fallDamage" // new + FireDamage gameRuleBoolName = "fireDamage" // new + KeepInventory gameRuleBoolName = "keepInventory" + LogAdminCommands gameRuleBoolName = "logAdminCommands" + MobGriefing gameRuleBoolName = "mobGriefing" + NaturalRegeneration gameRuleBoolName = "naturalRegeneration" + ReducedDebugInfo gameRuleBoolName = "reducedDebugInfo" + SendCommandFeedback gameRuleBoolName = "sendCommandFeedback" + ShowDeathMessages gameRuleBoolName = "showDeathMessages" + SpectatorsGenerateChunks gameRuleBoolName = "spectatorsGenerateChunks" +) + +type gameRuleIntName string + +func (gr gameRuleIntName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use integers +const ( + MaxCommandChainLength gameRuleIntName = "maxCommandChainLength" + MaxEntityCramming gameRuleIntName = "maxEntityCramming" + RandomTickSpeed gameRuleIntName = "randomTickSpeed" + SpawnRadius gameRuleIntName = "spawnRadius" +) + +// GameRuleName accepts either boolean or integer name types +type GameRuleName interface { + gameRuleName() string +} + +// GameRule is a command used to set various rules in game +type GameRule struct { + name GameRuleName + bVal bool + iVal int +} + +func NewGameRuleGet(name GameRuleName) GameRule { + return GameRule{name: name} +} + +func NewGameRuleBoolean(name GameRuleName, b bool) GameRule { + return GameRule{name: name, bVal: b} +} + +func NewGameRuleInt(name GameRuleName, i int) GameRule { + return GameRule{name: name, iVal: i} +} + +// Command allows the GameRule struct to be executed as a command in game +func (c GameRule) Command() string { + switch c.name.(type) { + case gameRuleBoolName: + return fmt.Sprintf("gamerule %s %t", c.name.gameRuleName(), c.bVal) + case gameRuleIntName: + return fmt.Sprintf("gamerule %s %d", c.name.gameRuleName(), c.iVal) + default: + return fmt.Sprintf("gamerule %s", c.name.gameRuleName()) + } +} + +func (c GameRule) Events() []wrapper.Event { + return []wrapper.Event{ + &GameRuleSet{}, + &GameRuleGet{}, + &wrapper.IncorrectCommandArgument{}, + &wrapper.InvalidBoolean{}, + &wrapper.InvalidInteger{}, + &wrapper.UnknownOrIncompleteCommand{}, + } +} + +type GameRuleSet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleSet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %d", event.iVal); err != nil { + return false + } + } + return true +} + +type GameRuleGet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleGet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %d", event.iVal); err != nil { + return false + } + } + return true +} diff --git a/commands/java/1.16.2/gamerule.go b/commands/java/1.16.2/gamerule.go new file mode 100644 index 0000000..4b6d982 --- /dev/null +++ b/commands/java/1.16.2/gamerule.go @@ -0,0 +1,137 @@ +package minecraft_1_16_2 + +import ( + "fmt" + + wrapper "github.com/wlwanpan/minecraft-wrapper" +) + +type gameRuleBoolName string + +func (gr gameRuleBoolName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use booleans +const ( + AnnounceAdvancements gameRuleBoolName = "announceAdvancements" + CommandBlockOutput gameRuleBoolName = "commandBlockOutput" + DisableElytraMovementCheck gameRuleBoolName = "disableElytraMovementCheck" + DisableRaids gameRuleBoolName = "disableRaids" + DoDaylightCycle gameRuleBoolName = "doDaylightCycle" + DoEntityDrops gameRuleBoolName = "doEntityDrops" + DoFireTick gameRuleBoolName = "doFireTick" + DoInsomnia gameRuleBoolName = "doInsomnia" + DoImmediateRespawn gameRuleBoolName = "doImmediateRespawn" + DoLimitedCrafting gameRuleBoolName = "doLimitedCrafting" + DoMobLoot gameRuleBoolName = "doMobLoot" + DoMobSpawning gameRuleBoolName = "doMobSpawning" + DoPatrolSpawning gameRuleBoolName = "doPatrolSpawning" + DoTileDrops gameRuleBoolName = "doTileDrops" + DoTraderSpawning gameRuleBoolName = "doTraderSpawning" + DoWeatherCycle gameRuleBoolName = "doWeatherCycle" + DrowningDamage gameRuleBoolName = "drowningDamage" + FallDamage gameRuleBoolName = "fallDamage" + FireDamage gameRuleBoolName = "fireDamage" + ForgiveDeadPlayers gameRuleBoolName = "forgiveDeadPlayers" + KeepInventory gameRuleBoolName = "keepInventory" + LogAdminCommands gameRuleBoolName = "logAdminCommands" + MobGriefing gameRuleBoolName = "mobGriefing" + NaturalRegeneration gameRuleBoolName = "naturalRegeneration" + ReducedDebugInfo gameRuleBoolName = "reducedDebugInfo" + SendCommandFeedback gameRuleBoolName = "sendCommandFeedback" + ShowDeathMessages gameRuleBoolName = "showDeathMessages" + SpectatorsGenerateChunks gameRuleBoolName = "spectatorsGenerateChunks" + UniversalAnger gameRuleBoolName = "universalAnger" +) + +type gameRuleIntName string + +func (gr gameRuleIntName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use integers +const ( + MaxCommandChainLength gameRuleIntName = "maxCommandChainLength" + MaxEntityCramming gameRuleIntName = "maxEntityCramming" + RandomTickSpeed gameRuleIntName = "randomTickSpeed" + SpawnRadius gameRuleIntName = "spawnRadius" +) + +// GameRuleName accepts either boolean or integer name types +type GameRuleName interface { + gameRuleName() string +} + +// GameRule is a command used to set various rules in game +type GameRule struct { + name GameRuleName + bVal bool + iVal int +} + +func NewGameRuleGet(name GameRuleName) GameRule { + return GameRule{name: name} +} + +func NewGameRuleBoolean(name GameRuleName, b bool) GameRule { + return GameRule{name: name, bVal: b} +} + +func NewGameRuleInt(name GameRuleName, i int) GameRule { + return GameRule{name: name, iVal: i} +} + +// Command allows the GameRule struct to be executed as a command in game +func (c GameRule) Command() string { + switch c.name.(type) { + case gameRuleBoolName: + return fmt.Sprintf("gamerule %s %t", c.name.gameRuleName(), c.bVal) + case gameRuleIntName: + return fmt.Sprintf("gamerule %s %d", c.name.gameRuleName(), c.iVal) + default: + return fmt.Sprintf("gamerule %s", c.name.gameRuleName()) + } +} + +func (c GameRule) Events() []wrapper.Event { + return []wrapper.Event{ + &GameRuleSet{}, + &GameRuleGet{}, + &wrapper.IncorrectCommandArgument{}, + &wrapper.InvalidBoolean{}, + &wrapper.InvalidInteger{}, + &wrapper.UnknownOrIncompleteCommand{}, + } +} + +type GameRuleSet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleSet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %d", event.iVal); err != nil { + return false + } + } + return true +} + +type GameRuleGet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleGet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %d", event.iVal); err != nil { + return false + } + } + return true +} diff --git a/commands/java/1.16/gamerule.go b/commands/java/1.16/gamerule.go new file mode 100644 index 0000000..6e0ac9e --- /dev/null +++ b/commands/java/1.16/gamerule.go @@ -0,0 +1,137 @@ +package minecraft_1_16 + +import ( + "fmt" + + wrapper "github.com/wlwanpan/minecraft-wrapper" +) + +type gameRuleBoolName string + +func (gr gameRuleBoolName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use booleans +const ( + AnnounceAdvancements gameRuleBoolName = "announceAdvancements" + CommandBlockOutput gameRuleBoolName = "commandBlockOutput" + DisableElytraMovementCheck gameRuleBoolName = "disableElytraMovementCheck" + DisableRaids gameRuleBoolName = "disableRaids" + DoDaylightCycle gameRuleBoolName = "doDaylightCycle" + DoEntityDrops gameRuleBoolName = "doEntityDrops" + DoFireTick gameRuleBoolName = "doFireTick" + DoInsomnia gameRuleBoolName = "doInsomnia" + DoImmediateRespawn gameRuleBoolName = "doImmediateRespawn" + DoLimitedCrafting gameRuleBoolName = "doLimitedCrafting" + DoMobLoot gameRuleBoolName = "doMobLoot" + DoMobSpawning gameRuleBoolName = "doMobSpawning" + DoPatrolSpawning gameRuleBoolName = "doPatrolSpawning" + DoTileDrops gameRuleBoolName = "doTileDrops" + DoTraderSpawning gameRuleBoolName = "doTraderSpawning" + DoWeatherCycle gameRuleBoolName = "doWeatherCycle" + DrowningDamage gameRuleBoolName = "drowningDamage" + FallDamage gameRuleBoolName = "fallDamage" + FireDamage gameRuleBoolName = "fireDamage" + ForgiveDeadPlayers gameRuleBoolName = "forgiveDeadPlayers" // new + KeepInventory gameRuleBoolName = "keepInventory" + LogAdminCommands gameRuleBoolName = "logAdminCommands" + MobGriefing gameRuleBoolName = "mobGriefing" + NaturalRegeneration gameRuleBoolName = "naturalRegeneration" + ReducedDebugInfo gameRuleBoolName = "reducedDebugInfo" + SendCommandFeedback gameRuleBoolName = "sendCommandFeedback" + ShowDeathMessages gameRuleBoolName = "showDeathMessages" + SpectatorsGenerateChunks gameRuleBoolName = "spectatorsGenerateChunks" + UniversalAnger gameRuleBoolName = "universalAnger" // new +) + +type gameRuleIntName string + +func (gr gameRuleIntName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use integers +const ( + MaxCommandChainLength gameRuleIntName = "maxCommandChainLength" + MaxEntityCramming gameRuleIntName = "maxEntityCramming" + RandomTickSpeed gameRuleIntName = "randomTickSpeed" + SpawnRadius gameRuleIntName = "spawnRadius" +) + +// GameRuleName accepts either boolean or integer name types +type GameRuleName interface { + gameRuleName() string +} + +// GameRule is a command used to set various rules in game +type GameRule struct { + name GameRuleName + bVal bool + iVal int +} + +func NewGameRuleGet(name GameRuleName) GameRule { + return GameRule{name: name} +} + +func NewGameRuleBoolean(name GameRuleName, b bool) GameRule { + return GameRule{name: name, bVal: b} +} + +func NewGameRuleInt(name GameRuleName, i int) GameRule { + return GameRule{name: name, iVal: i} +} + +// Command allows the GameRule struct to be executed as a command in game +func (c GameRule) Command() string { + switch c.name.(type) { + case gameRuleBoolName: + return fmt.Sprintf("gamerule %s %t", c.name.gameRuleName(), c.bVal) + case gameRuleIntName: + return fmt.Sprintf("gamerule %s %d", c.name.gameRuleName(), c.iVal) + default: + return fmt.Sprintf("gamerule %s", c.name.gameRuleName()) + } +} + +func (c GameRule) Events() []wrapper.Event { + return []wrapper.Event{ + &GameRuleSet{}, + &GameRuleGet{}, + &wrapper.IncorrectCommandArgument{}, + &wrapper.InvalidBoolean{}, + &wrapper.InvalidInteger{}, + &wrapper.UnknownOrIncompleteCommand{}, + } +} + +type GameRuleSet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleSet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %d", event.iVal); err != nil { + return false + } + } + return true +} + +type GameRuleGet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleGet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %d", event.iVal); err != nil { + return false + } + } + return true +} diff --git a/commands/java/1.17/gamerule.go b/commands/java/1.17/gamerule.go new file mode 100644 index 0000000..c51141b --- /dev/null +++ b/commands/java/1.17/gamerule.go @@ -0,0 +1,139 @@ +package minecraft_1_17 + +import ( + "fmt" + + wrapper "github.com/wlwanpan/minecraft-wrapper" +) + +type gameRuleBoolName string + +func (gr gameRuleBoolName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use booleans +const ( + AnnounceAdvancements gameRuleBoolName = "announceAdvancements" + CommandBlockOutput gameRuleBoolName = "commandBlockOutput" + DisableElytraMovementCheck gameRuleBoolName = "disableElytraMovementCheck" + DisableRaids gameRuleBoolName = "disableRaids" + DoDaylightCycle gameRuleBoolName = "doDaylightCycle" + DoEntityDrops gameRuleBoolName = "doEntityDrops" + DoFireTick gameRuleBoolName = "doFireTick" + DoInsomnia gameRuleBoolName = "doInsomnia" + DoImmediateRespawn gameRuleBoolName = "doImmediateRespawn" + DoLimitedCrafting gameRuleBoolName = "doLimitedCrafting" + DoMobLoot gameRuleBoolName = "doMobLoot" + DoMobSpawning gameRuleBoolName = "doMobSpawning" + DoPatrolSpawning gameRuleBoolName = "doPatrolSpawning" + DoTileDrops gameRuleBoolName = "doTileDrops" + DoTraderSpawning gameRuleBoolName = "doTraderSpawning" + DoWeatherCycle gameRuleBoolName = "doWeatherCycle" + DrowningDamage gameRuleBoolName = "drowningDamage" + FallDamage gameRuleBoolName = "fallDamage" + FireDamage gameRuleBoolName = "fireDamage" + ForgiveDeadPlayers gameRuleBoolName = "forgiveDeadPlayers" + FreezeDamage gameRuleBoolName = "freezeDamage" // new + KeepInventory gameRuleBoolName = "keepInventory" + LogAdminCommands gameRuleBoolName = "logAdminCommands" + MobGriefing gameRuleBoolName = "mobGriefing" + NaturalRegeneration gameRuleBoolName = "naturalRegeneration" + ReducedDebugInfo gameRuleBoolName = "reducedDebugInfo" + SendCommandFeedback gameRuleBoolName = "sendCommandFeedback" + ShowDeathMessages gameRuleBoolName = "showDeathMessages" + SpectatorsGenerateChunks gameRuleBoolName = "spectatorsGenerateChunks" + UniversalAnger gameRuleBoolName = "universalAnger" +) + +type gameRuleIntName string + +func (gr gameRuleIntName) gameRuleName() string { + return string(gr) +} + +// GameRule Names that use integers +const ( + MaxCommandChainLength gameRuleIntName = "maxCommandChainLength" + MaxEntityCramming gameRuleIntName = "maxEntityCramming" + PlayersSleepingPercentage gameRuleIntName = "playersSleepingPercentage" // new + RandomTickSpeed gameRuleIntName = "randomTickSpeed" + SpawnRadius gameRuleIntName = "spawnRadius" +) + +// GameRuleName accepts either boolean or integer name types +type GameRuleName interface { + gameRuleName() string +} + +// GameRule is a command used to set various rules in game +type GameRule struct { + name GameRuleName + bVal bool + iVal int +} + +func NewGameRuleGet(name GameRuleName) GameRule { + return GameRule{name: name} +} + +func NewGameRuleBoolean(name GameRuleName, b bool) GameRule { + return GameRule{name: name, bVal: b} +} + +func NewGameRuleInt(name GameRuleName, i int) GameRule { + return GameRule{name: name, iVal: i} +} + +// Command allows the GameRule struct to be executed as a command in game +func (c GameRule) Command() string { + switch c.name.(type) { + case gameRuleBoolName: + return fmt.Sprintf("gamerule %s %t", c.name.gameRuleName(), c.bVal) + case gameRuleIntName: + return fmt.Sprintf("gamerule %s %d", c.name.gameRuleName(), c.iVal) + default: + return fmt.Sprintf("gamerule %s", c.name.gameRuleName()) + } +} + +func (c GameRule) Events() []wrapper.Event { + return []wrapper.Event{ + &GameRuleSet{}, + &GameRuleGet{}, + &wrapper.IncorrectCommandArgument{}, + &wrapper.InvalidBoolean{}, + &wrapper.InvalidInteger{}, + &wrapper.UnknownOrIncompleteCommand{}, + } +} + +type GameRuleSet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleSet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is now set to: %d", event.iVal); err != nil { + return false + } + } + return true +} + +type GameRuleGet struct { + name GameRuleName + bVal *bool + iVal *int +} + +func (event *GameRuleGet) Parse(s string) bool { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %T", event.bVal); err != nil { + if _, err := fmt.Sscanf(s, "Gamerule %s is currently set to: %d", event.iVal); err != nil { + return false + } + } + return true +} diff --git a/events.go b/events.go new file mode 100644 index 0000000..6a463cd --- /dev/null +++ b/events.go @@ -0,0 +1,41 @@ +package wrapper + +import ( + "fmt" +) + +type Event interface { + Parse(string) bool +} + +type IncorrectCommandArgument struct{} + +func (event *IncorrectCommandArgument) Parse(s string) bool { + return s != "Incorrect argument for command" +} + +type InvalidBoolean struct { + Value string +} + +func (event *InvalidBoolean) Parse(s string) bool { + _, err := fmt.Sscanf(s, `Invalid boolean, expected 'true' or 'false' but found '%s'`, &event.Value) + return err == nil +} + +type InvalidInteger struct { + Value string // must be string in case an unrealistically large number is used +} + +func (event *InvalidInteger) Parse(s string) bool { + if _, err := fmt.Sscanf(s, `Invalid integer '%s'`, &event.Value); err != nil { + return false + } + return true +} + +type UnknownOrIncompleteCommand struct{} + +func (event *UnknownOrIncompleteCommand) Parse(s string) bool { + return s != "Unknown or incomplete command, see below for error" +} diff --git a/go.mod b/go.mod index 16c47a6..78082e6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/wlwanpan/minecraft-wrapper go 1.12 require ( - github.com/looplab/fsm v0.1.0 - github.com/mitchellh/mapstructure v1.4.0 + github.com/looplab/fsm v0.2.0 + github.com/mitchellh/mapstructure v1.4.1 + github.com/satori/go.uuid v1.2.0 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index eaeeed4..5b48bca 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,21 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= +github.com/looplab/fsm v0.2.0 h1:M8hf5EF4AYLcT1FNKVUX8nu7D0xfp291iGeuigSxfrw= +github.com/looplab/fsm v0.2.0/go.mod h1:p+IElwgCnAByqr2DWMuNbPjgMwqcHvTRZZn3dvKEke0= github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/minecraft_entities.go b/minecraft_entities.go new file mode 100644 index 0000000..1f6a5fe --- /dev/null +++ b/minecraft_entities.go @@ -0,0 +1,144 @@ +package wrapper + +type minecraftEntityID string + +func (id minecraftEntityID) Namespace() string { + return "minecraft" +} + +func (id minecraftEntityID) Name() string { + return string(id) +} + +func (id minecraftEntityID) String() string { + return id.Namespace() + ":" + id.Name() +} + +// Constants of minecraft entity IDs +const ( + AreaEffectCloudEntity minecraftEntityID = "area_effect_cloud" + ArmorStandEntity minecraftEntityID = "armor_stand" + ArrowEntity minecraftEntityID = "arrow" + BatEntity minecraftEntityID = "bat" + BeeEntity minecraftEntityID = "bee" + BlazeEntity minecraftEntityID = "blaze" + BoatEntity minecraftEntityID = "boat" + CatEntity minecraftEntityID = "cat" + CaveSpiderEntity minecraftEntityID = "cave_spider" + ChestMinecartEntity minecraftEntityID = "chest_minecart" + ChickenEntity minecraftEntityID = "chicken" + CodEntity minecraftEntityID = "cod" + CommandBlockMinecartEntity minecraftEntityID = "command_block_minecart" + CowEntity minecraftEntityID = "cow" + CreeperEntity minecraftEntityID = "creeper" + DolphinEntity minecraftEntityID = "dolphin" + DonkeyEntity minecraftEntityID = "donkey" + DragonFireballEntity minecraftEntityID = "dragon_fireball" + DrownedEntity minecraftEntityID = "drowned" + EggEntity minecraftEntityID = "egg" + ElderGuardianEntity minecraftEntityID = "elder_guardian" + EndCrystalEntity minecraftEntityID = "end_crystal" + EnderDragonEntity minecraftEntityID = "ender_dragon" + EnderPearlEntity minecraftEntityID = "ender_pearl" + EndermanEntity minecraftEntityID = "enderman" + EndermiteEntity minecraftEntityID = "endermite" + EvokerEntity minecraftEntityID = "evoker" + EvokerFangsEntity minecraftEntityID = "evoker_fangs" + ExperienceBottleEntity minecraftEntityID = "experience_bottle" + ExperienceOrbEntity minecraftEntityID = "experience_orb" + EyeOfEnderEntity minecraftEntityID = "eye_of_ender" + FallingBlockEntity minecraftEntityID = "falling_block" + FireballEntity minecraftEntityID = "fireball" + FireworkRocketEntity minecraftEntityID = "firework_rocket" + FishingBobberEntity minecraftEntityID = "fishing_bobber" + FoxEntity minecraftEntityID = "fox" + FurnaceMinecartEntity minecraftEntityID = "furnace_minecart" + GhastEntity minecraftEntityID = "ghast" + GiantEntity minecraftEntityID = "giant" + GuardianEntity minecraftEntityID = "guardian" + HopperMinecartEntity minecraftEntityID = "hopper_minecart" + HorseEntity minecraftEntityID = "horse" + HuskEntity minecraftEntityID = "husk" + IllusionerEntity minecraftEntityID = "illusioner" + IronGolemEntity minecraftEntityID = "iron_golem" + ItemEntity minecraftEntityID = "item" + ItemFrameEntity minecraftEntityID = "item_frame" + LeashKnotEntity minecraftEntityID = "leash_knot" + LightningBoltEntity minecraftEntityID = "lightning_bolt" + LlamaEntity minecraftEntityID = "llama" + LlamaSpitEntity minecraftEntityID = "llama_spit" + MagmaCubeEntity minecraftEntityID = "magma_cube" + MinecartEntity minecraftEntityID = "minecart" + MooshroomEntity minecraftEntityID = "mooshroom" + MuleEntity minecraftEntityID = "mule" + OcelotEntity minecraftEntityID = "ocelot" + PaintingEntity minecraftEntityID = "painting" + PandaEntity minecraftEntityID = "panda" + ParrotEntity minecraftEntityID = "parrot" + PhantomEntity minecraftEntityID = "phantom" + PigEntity minecraftEntityID = "pig" + PillagerEntity minecraftEntityID = "pillager" + PlayerEntity minecraftEntityID = "player" + PolarBearEntity minecraftEntityID = "polar_bear" + PotionEntity minecraftEntityID = "potion" + PufferfishEntity minecraftEntityID = "pufferfish" + RabbitEntity minecraftEntityID = "rabbit" + RavagerEntity minecraftEntityID = "ravager" + SalmonEntity minecraftEntityID = "salmon" + SheepEntity minecraftEntityID = "sheep" + ShulkerEntity minecraftEntityID = "shulker" + ShulkerBulletEntity minecraftEntityID = "shukler_bullet" + SilverfishEntity minecraftEntityID = "silverfish" + SkeletonEntity minecraftEntityID = "skeleton" + SkeletonHorseEntity minecraftEntityID = "skeleton_horse" + SlimeEntity minecraftEntityID = "slime" + SmallFireballEntity minecraftEntityID = "small_fireball" + SnowGolemEntity minecraftEntityID = "snow_golem" + SnowballEntity minecraftEntityID = "snowball" + SpawnerMinecartEntity minecraftEntityID = "spawner_minecart" + SpectralArrowEntity minecraftEntityID = "spectral_arrow" + SpiderEntity minecraftEntityID = "spider" + SquidEntity minecraftEntityID = "squid" + TNTEntity minecraftEntityID = "tnt" + TNTMinecartEntity minecraftEntityID = "tnt_minecart" + TraderLlamaEntity minecraftEntityID = "trader_llama" + TridentEntity minecraftEntityID = "trident" + TropicalFishEntity minecraftEntityID = "tropical_fish" + TurtleEntity minecraftEntityID = "turtle" + VexEntity minecraftEntityID = "vex" + VillagerEntity minecraftEntityID = "villager" + VindicatorEntity minecraftEntityID = "vindicator" + WanderingTraderEntity minecraftEntityID = "wandering_trader" + WitchEntity minecraftEntityID = "witch" + WitherEntity minecraftEntityID = "wither" + WitherSkeletonEntity minecraftEntityID = "wither_skeleton" + WitherSkullEntity minecraftEntityID = "wither_skull" + WolfEntity minecraftEntityID = "wolf" + ZombieEntity minecraftEntityID = "zombie" + ZombieHorseEntity minecraftEntityID = "zombie_horse" + ZombiePigmanEntity minecraftEntityID = "zombie_pigman" + ZombieVillagerEntity minecraftEntityID = "zombie_villager" +) + +type minecraftEntityTypeTag string + +func (tag minecraftEntityTypeTag) Namespace() string { + return "minecaft" +} + +func (tag minecraftEntityTypeTag) Name() string { + return string(tag) +} + +func (tag minecraftEntityTypeTag) String() string { + return "#" + tag.Namespace() + ":" + tag.Name() +} + +// Constants of minecraft entity tags +const ( + Arrows minecraftEntityTypeTag = "arrows" + BeehiveInhabitors minecraftEntityTypeTag = "beehive_inhabitors" + ImpactProjectiles minecraftEntityTypeTag = "impact_projectiles" + Raiders minecraftEntityTypeTag = "raiders" + Skeletons minecraftEntityTypeTag = "skeletons" +) diff --git a/target_selector_test.go b/target_selector_test.go new file mode 100644 index 0000000..a721140 --- /dev/null +++ b/target_selector_test.go @@ -0,0 +1,709 @@ +package wrapper + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +/*//////////////////////////// +//////// Suite Setup //////// +////////////////////////////*/ + +type TargetSelectorSuite struct { + suite.Suite +} + +func (s *TargetSelectorSuite) SetupSuite() {} + +func (s *TargetSelectorSuite) SetupTest() {} + +func (s *TargetSelectorSuite) TearDownTest() {} + +func (s *TargetSelectorSuite) TearDownSuite() {} + +func TestTargetSelectorSuite(t *testing.T) { + suite.Run(t, new(TargetSelectorSuite)) +} + +/*//////////////////////////// +// TargetSelectorType Test // +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestTargetSelectorType() { + type testcase struct { + Name string + TargetSelectorType TargetSelectorType + TargetOutput string + } + + cases := []testcase{ + { + Name: "AllPlayers", + TargetSelectorType: AllPlayers, + TargetOutput: "@a[]", + }, + { + Name: "AllEntities", + TargetSelectorType: AllEntities, + TargetOutput: "@e[]", + }, + { + Name: "NearestPlayer", + TargetSelectorType: NearestPlayer, + TargetOutput: "@p[]", + }, + { + Name: "RandomPlayer", + TargetSelectorType: RandomPlayer, + TargetOutput: "@r[]", + }, + { + Name: "ExecutingEntity", + TargetSelectorType: ExecutingEntity, + TargetOutput: "@s[]", + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + TargetSelector{t: c.TargetSelectorType}.String(), + ) + }) + } +} + +/*//////////////////////////// +//// WithPositional Test //// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithPositional() { + type testcase struct { + Name string + Positional Positional + TargetOutput string + } + + cases := []testcase{ + { + Name: "X", + Positional: Positional{ + Type: X, + Value: 0, + Relative: true, + }, + TargetOutput: "@e[x=~0.00]", + }, + { + Name: "Y", + Positional: Positional{ + Type: Y, + Value: 0, + Relative: false, + }, + TargetOutput: "@e[y=0.00]", + }, + { + Name: "Z", + Positional: Positional{ + Type: Z, + Value: 100.0001, + Relative: true, + }, + TargetOutput: "@e[z=~100.00]", + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithPositional(c.Positional).String(), + ) + }) + } +} + +/*//////////////////////////// +///// WithDistance Test ///// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithDistance() { + type testcase struct { + Name string + Distance Distance + TargetOutput string + } + + cases := []testcase{ + { + Name: "Exact", + Distance: &ExactDistance{10}, + TargetOutput: "@e[distance=10]", + }, + { + Name: "Range", + Distance: &RangeDistance{10, 20}, + TargetOutput: "@e[distance=10..20]", + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithDistance(c.Distance).String(), + ) + }) + } +} + +/*//////////////////////////// +////// WithVolume Test ////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithVolume() { + type testcase struct { + Name string + Volume Volume + TargetOutput string + } + + cases := []testcase{ + { + Name: "DX", + Volume: Volume{ + Type: DX, + Value: 10, + }, + TargetOutput: "@e[dx=10.00]", + }, + { + Name: "DY", + Volume: Volume{ + Type: DY, + Value: 10, + }, + TargetOutput: "@e[dy=10.00]", + }, + { + Name: "DZ", + Volume: Volume{ + Type: DZ, + Value: 10, + }, + TargetOutput: "@e[dz=10.00]", + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithVolume(c.Volume).String(), + ) + }) + } +} + +/*//////////////////////////// +////// WithScore Test ////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithScore() { + type testcase struct { + Name string + Score []Score + TargetOutput string + } + + cases := []testcase{ + { + Name: "Exact", + Score: []Score{ + ExactScore{ + Objective: "test", + Value: 10, + }, + }, + TargetOutput: `@e[score={test=10}]`, + }, + { + Name: "Range", + Score: []Score{ + RangeScore{ + Objective: "test", + Min: 10, + Max: 20, + }, + }, + TargetOutput: "@e[score={test=10..20}]", + }, + { + Name: "Multi", + Score: []Score{ + ExactScore{ + Objective: "a", + Value: 10, + }, + RangeScore{ + Objective: "b", + Min: 10, + Max: 20, + }, + }, + TargetOutput: "@e[score={a=10,b=10..20}]", + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithScore(c.Score...).String(), + ) + }) + } +} + +/*//////////////////////////// +/////// WithTeam Test /////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithTeam() { + type testcase struct { + Name string + Team string + Not bool + TargetOutput string + } + + cases := []testcase{ + { + Name: "TeamA", + Team: "A", + Not: false, + TargetOutput: `@e[team=A]`, + }, + { + Name: "NotTeamA", + Team: "A", + Not: true, + TargetOutput: `@e[team=!A]`, + }, + { + Name: "Teamless", + Team: "", + Not: false, + TargetOutput: `@e[team=]`, + }, + { + Name: "NotTeamless", + Team: "", + Not: true, + TargetOutput: `@e[team=!]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithTeam(c.Team, c.Not).String(), + ) + }) + } +} + +/*//////////////////////////// +/////// WithLimit Test /////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithLimit() { + type testcase struct { + Name string + Limit uint + Sort sortType + TargetOutput string + } + + cases := []testcase{ + { + Name: "WithoutSort", + Limit: 10, + Sort: NoSort, + TargetOutput: `@e[limit=10]`, + }, + { + Name: "WithSort", + Limit: 10, + Sort: Arbitrary, + // TODO: Map order is not guaranteed, needs fixing + TargetOutput: `@e[limit=10,sort=arbitrary]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithLimit(c.Limit, c.Sort).String(), + ) + }) + } +} + +/*//////////////////////////// +//// WithExperience Test //// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithExperience() { + type testcase struct { + Name string + Experience Experience + TargetOutput string + } + + cases := []testcase{ + { + Name: "ExactExperience", + Experience: &ExactExperience{10}, + TargetOutput: `@e[level=10]`, + }, + { + Name: "RangeExperience", + Experience: &RangeExperience{10, 20}, + TargetOutput: `@e[level=10..20]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithExperience(c.Experience).String(), + ) + }) + } +} + +/*//////////////////////////// +///// WithGameMode Test ///// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithGameMode() { + type testcase struct { + Name string + GameMode GameMode + Not bool + TargetOutput string + } + + cases := []testcase{ + { + Name: "Spectator", + GameMode: Spectator, + Not: false, + TargetOutput: `@e[gamemode=spectator]`, + }, + { + Name: "NotSpectator", + GameMode: Spectator, + Not: true, + TargetOutput: `@e[gamemode=!spectator]`, + }, + { + Name: "Adventure", + GameMode: Adventure, + Not: false, + TargetOutput: `@e[gamemode=adventure]`, + }, + { + Name: "NotAdventure", + GameMode: Adventure, + Not: true, + TargetOutput: `@e[gamemode=!adventure]`, + }, + { + Name: "Creative", + GameMode: Creative, + Not: false, + TargetOutput: `@e[gamemode=creative]`, + }, + { + Name: "NotCreative", + GameMode: Creative, + Not: true, + TargetOutput: `@e[gamemode=!creative]`, + }, + { + Name: "Survival", + GameMode: Survival, + Not: false, + TargetOutput: `@e[gamemode=survival]`, + }, + { + Name: "NotSurvival", + GameMode: Survival, + Not: true, + TargetOutput: `@e[gamemode=!survival]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithGameMode(c.GameMode, c.Not).String(), + ) + }) + } +} + +/*//////////////////////////// +/////// WithName Test /////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithName() { + type testcase struct { + Name string + PlayerName string + Not bool + TargetOutput string + } + + cases := []testcase{ + { + Name: "Name", + PlayerName: "Steve", + Not: false, + TargetOutput: `@e[name=Steve]`, + }, + { + Name: "NotName", + PlayerName: "Steve", + Not: true, + TargetOutput: `@e[name=!Steve]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithName(c.PlayerName, c.Not).String(), + ) + }) + } +} + +/*//////////////////////////// +///// WithRotation Test ///// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithRotation() { + type testcase struct { + Name string + Rotation Rotation + TargetOutput string + } + + cases := []testcase{ + { + Name: "Exact", + Rotation: &ExactRotation{XRotation, 10}, + TargetOutput: `@e[x_rotation=10.00]`, + }, + { + Name: "Range", + Rotation: &RangeRotation{YRotation, NewFloat64(10), NewFloat64(20)}, + TargetOutput: `@e[y_rotation=10.00..20.00]`, + }, + { + Name: "RangeMax", + Rotation: &RangeRotation{YRotation, nil, NewFloat64(20)}, + TargetOutput: `@e[y_rotation=..20.00]`, + }, + { + Name: "RangeMin", + Rotation: &RangeRotation{YRotation, NewFloat64(10), nil}, + TargetOutput: `@e[y_rotation=10.00..]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithRotation(c.Rotation).String(), + ) + }) + } +} + +/*//////////////////////////// +/////// WithType Test /////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithType() { + type testcase struct { + Name string + Types []TypeArg + TargetOutput string + } + + cases := []testcase{ + { + Name: "None", + Types: nil, + TargetOutput: `@e[]`, + }, + { + Name: "One", + Types: []TypeArg{ + { + Type: PlayerEntity, + Not: false, + }, + }, + TargetOutput: `@e[type=minecraft:player]`, + }, + { + Name: "NotOne", + Types: []TypeArg{ + { + Type: PlayerEntity, + Not: true, + }, + }, + TargetOutput: `@e[type=!minecraft:player]`, + }, + { + Name: "Multiple", + Types: []TypeArg{ + { + Type: PlayerEntity, + Not: true, + }, + { + Type: EnderDragonEntity, + Not: false, + }, + }, + TargetOutput: `@e[type=!minecraft:player,type=minecraft:ender_dragon]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithType(c.Types...).String(), + ) + }) + } +} + +/*//////////////////////////// +/////// WithType Test /////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestWithDataTag() { + type testcase struct { + Name string + Tags []Tag + TargetOutput string + } + + cases := []testcase{ + { + Name: "None", + Tags: nil, + TargetOutput: `@e[]`, + }, + { + Name: "One", + Tags: []Tag{ + { + Name: "A", + Not: false, + }, + }, + TargetOutput: `@e[tag=A]`, + }, + { + Name: "NotOne", + Tags: []Tag{ + { + Name: "A", + Not: true, + }, + }, + TargetOutput: `@e[tag=!A]`, + }, + { + Name: "Multiple", + Tags: []Tag{ + { + Name: "A", + Not: true, + }, + { + Name: "B", + Not: false, + }, + }, + TargetOutput: `@e[tag=!A,tag=B]`, + }, + } + + for _, c := range cases { + s.Run(c.Name, func() { + s.Assert().Equal( + c.TargetOutput, + NewTargetSelector(AllEntities).WithDataTag(c.Tags...).String(), + ) + }) + } +} + +/*//////////////////////////// +//////// All Example //////// +////////////////////////////*/ + +func (s *TargetSelectorSuite) TestAll() { + expected := TargetSelector{ + t: AllEntities, + args: map[string]string{ + "x": "1.00", + "distance": "2", + "dz": "3.00", + "score": "{score=4..5}", + "team": "!team", + "limit": "6", + "sort": "arbitrary", + "level": "7", + "gamemode": "survival", + "y_rotation": "..8.00", + }, + types: []string{"!minecraft:player"}, + tags: []string{"tag"}, + } + + actual := NewTargetSelector(AllEntities). + WithPositional(Positional{X, 1, false}). + WithDistance(ExactDistance{2}). + WithVolume(Volume{DZ, 3}). + WithScore(RangeScore{"score", 4, 5}). + WithTeam("team", true). + WithLimit(6, Arbitrary). + WithExperience(ExactExperience{7}). + WithGameMode(Survival, false). + WithRotation(RangeRotation{YRotation, nil, NewFloat64(8)}). + WithType(TypeArg{PlayerEntity, true}). + WithDataTag(Tag{"tag", false}) + + s.Assert().EqualValues(expected, actual) +} diff --git a/target_selectors.go b/target_selectors.go new file mode 100644 index 0000000..45fec18 --- /dev/null +++ b/target_selectors.go @@ -0,0 +1,431 @@ +package wrapper + +import ( + "fmt" + "strings" +) + +// TargetSelectorType - an enum type for the 5 different target selectors +type TargetSelectorType string + +const ( + // AllPlayers - targets every player (alive or dead) by default + AllPlayers TargetSelectorType = "a" + // AllEntities - targets all alive entities in loaded chunks (includes players) + AllEntities TargetSelectorType = "e" + // NearestPlayer - targets the nearest player. When run by the console, + // the origin of selection is (0,0,0). If there are multiple nearest + // players, caused by them being precisely the same distance away, the + // payer who most recently joined the server is selected + NearestPlayer TargetSelectorType = "p" + // RandomPlayer - targets a random player + RandomPlayer TargetSelectorType = "r" + // ExecutingEntity - targets the entity (alive or dead) that executed + // the command. It does not target anyhing if the command was run by a + // command block or server console + ExecutingEntity TargetSelectorType = "s" +) + +// TargetSelector - defines and creates a TargetSelector, +// complete with any arguments defined through argument functions +type TargetSelector struct { + t TargetSelectorType + args map[string]string + types []string + tags []string +} + +// NewTargetSelector - creates a new TargetSelector of the specified type +func NewTargetSelector(t TargetSelectorType) TargetSelector { + return TargetSelector{ + t: t, + args: make(map[string]string), + } +} + +// String - returns, in the expected minecraft console format, the string +// version of the TargetSelector +func (s TargetSelector) String() string { + var allArgs []string = make([]string, 0, len(s.args)+len(s.types)+len(s.tags)) + + // args + for key, value := range s.args { + allArgs = append(allArgs, fmt.Sprintf("%s=%s", key, value)) + } + + // types + for _, t := range s.types { + allArgs = append(allArgs, "type="+t) + } + + // tags + for _, tag := range s.tags { + allArgs = append(allArgs, "tag="+tag) + } + + return fmt.Sprintf("@%s[%s]", s.t, strings.Join(allArgs, ",")) +} + +func (s TargetSelector) copyArgs() { + var newMap = make(map[string]string) + for key, val := range s.args { + newMap[key] = val + } + s.args = newMap +} + +func (s TargetSelector) copyStringSlice(slice []string) []string { + return append([]string{}, slice...) +} + +type positionalType string + +// Positional argument types +const ( + X positionalType = "x" + Y positionalType = "y" + Z positionalType = "z" +) + +// Positional - defines a positional argument for TargetSelectors +type Positional struct { + Type positionalType + Value float64 + Relative bool +} + +// WithPositional - adds positional argument(s) to the TargetSelector. +// Returns a new TargetSelector with the positional argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithPositional(args ...Positional) TargetSelector { + s.copyArgs() + for _, arg := range args { + if arg.Relative { + s.args[string(arg.Type)] = fmt.Sprintf("~%.2f", arg.Value) + } else { + s.args[string(arg.Type)] = fmt.Sprintf("%.2f", arg.Value) + } + } + return s +} + +// ExactDistance - specifes the exact distance targets must be from the point of command origin +type ExactDistance struct { + Value uint +} + +func (s ExactDistance) distance() string { + return fmt.Sprintf("%d", s.Value) +} + +// RangeDistance - specifes a range of distance targets can be from the point of command origin +type RangeDistance struct { + Min, Max uint +} + +func (s RangeDistance) distance() string { + return fmt.Sprintf("%d..%d", s.Min, s.Max) +} + +// Distance - accepts either Exacct or Range Distance types +type Distance interface { + distance() string +} + +// WithDistance - adds a diustance argument to the TargetSelector. +// Returns a new TargetSelector with the distance argument added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithDistance(distance Distance) TargetSelector { + s.copyArgs() + s.args["distance"] = distance.distance() + return s +} + +type volumeType string + +// Volume argument types +const ( + DX volumeType = "dx" + DY volumeType = "dy" + DZ volumeType = "dz" +) + +// Volume - defines a volume argument for TargetSelectors +type Volume struct { + Type volumeType + Value float64 +} + +// WithVolume - adds volume argument(s) to the TargetSelector. +// Returns a new TargetSelector with the volume argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithVolume(args ...Volume) TargetSelector { + s.copyArgs() + for _, arg := range args { + s.args[string(arg.Type)] = fmt.Sprintf("%.2f", arg.Value) + } + return s +} + +// ExactScore - specifes the exact score(s) targets must have +type ExactScore struct { + Objective string + Value int +} + +func (s ExactScore) score() string { + return fmt.Sprintf("%s=%d", s.Objective, s.Value) +} + +// RangeScore - specifes a range of score(s) targets can have +type RangeScore struct { + Objective string + Min, Max int +} + +func (s RangeScore) score() string { + return fmt.Sprintf("%s=%d..%d", s.Objective, s.Min, s.Max) +} + +// Score - accepts either an Exact or Range Score types +type Score interface { + score() string +} + +// WithScore - adds score argument(s) to the TargetSelector. +// Returns a new TargetSelector with the score argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithScore(scores ...Score) TargetSelector { + s.copyArgs() + var scoreStrings []string + for _, score := range scores { + scoreStrings = append(scoreStrings, score.score()) + } + scoresJoined := strings.Join(scoreStrings, ",") + s.args["score"] = fmt.Sprintf("{%s}", scoresJoined) + return s +} + +// WithTeam - adds a team argument to the TargetSelector. +// Empty teamName disginates those not on a team. +// Returns a new TargetSelector with the team argument added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithTeam(teamName string, not bool) TargetSelector { + s.copyArgs() + if not { + s.args["team"] = "!" + teamName + } else { + s.args["team"] = teamName + } + return s +} + +type sortType string + +// Sort argument types +const ( + NoSort sortType = "" + Nearest sortType = "nearest" + Furthest sortType = "furthest" + Random sortType = "random" + Arbitrary sortType = "arbitrary" +) + +// WithLimit - adds limit and optionally sort arguments to the TargetSelector. +// Returns a new TargetSelector with the argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithLimit(value uint, sort sortType) TargetSelector { + s.copyArgs() + s.args["limit"] = fmt.Sprintf("%d", value) + if sort != NoSort { + s.args["sort"] = string(sort) + } else { + delete(s.args, "sort") + } + return s +} + +// ExactExperience - specifies the exact level players must be +type ExactExperience struct { + Value uint +} + +func (e ExactExperience) experience() string { + return fmt.Sprintf("%d", e.Value) +} + +// RangeExperience - specifes a range of levels players may be +type RangeExperience struct { + Min, Max uint +} + +func (e RangeExperience) experience() string { + return fmt.Sprintf("%d..%d", e.Min, e.Max) +} + +// Experience - accepts either an Exact or Range Experience types +type Experience interface { + experience() string +} + +// WithExperience - adds limit and optionally sort arguments to the TargetSelector. +// Returns a new TargetSelector with the argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithExperience(exp Experience) TargetSelector { + s.copyArgs() + s.args["level"] = exp.experience() + return s +} + +// WithGameMode - adds a gamemode argument to the TargetSelector. +// Returns a new TargetSelector with the argument added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithGameMode(mode GameMode, not bool) TargetSelector { + s.copyArgs() + if not { + s.args["gamemode"] = "!" + string(mode) + } else { + s.args["gamemode"] = string(mode) + } + return s +} + +// WithName - adds a name argument to the TargetSelector. +// Returns a new TargetSelector with the argument added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithName(name string, not bool) TargetSelector { + s.copyArgs() + if not { + s.args["name"] = "!" + name + } else { + s.args["name"] = name + } + return s +} + +type rotationType string + +// Rotation argument types +const ( + XRotation rotationType = "x_rotation" + YRotation rotationType = "y_rotation" +) + +// ExactRotation - specifies the exact rotation entities must be facing +type ExactRotation struct { + Type rotationType + Value float64 +} + +func (r ExactRotation) rotationType() rotationType { + return r.Type +} + +func (r ExactRotation) rotation() string { + return fmt.Sprintf("%.2f", r.Value) +} + +// NewFloat64 - returns a pointer to the float value passed as an argument +func NewFloat64(f float64) *float64 { return &f } + +// RangeRotation - specifies the range of rotation entities may be facing +type RangeRotation struct { + Type rotationType + Min, Max *float64 +} + +func (r RangeRotation) rotationType() rotationType { + return r.Type +} + +func (r RangeRotation) rotation() string { + var rotStr string + if r.Min != nil { + rotStr += fmt.Sprintf("%.2f", *r.Min) + } + rotStr += ".." + if r.Max != nil { + rotStr += fmt.Sprintf("%.2f", *r.Max) + } + return rotStr +} + +// Rotation - accepts either Exact or Range Rotation types +type Rotation interface { + rotationType() rotationType + rotation() string +} + +// WithRotation - adds rotation argument(s) to the TargetSelector. +// Returns a new TargetSelector with the argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithRotation(rotations ...Rotation) TargetSelector { + s.copyArgs() + for _, rotation := range rotations { + key := rotation.rotationType() + val := rotation.rotation() + s.args[string(key)] = val + } + return s +} + +// TargetSelectorEntityType - an interface for entities, which consist of a namespace and name +// as well as a string format for use in commands. Public so users can satisfy the interface +// for mods and data packs which introduce their own entities. Built-in Minecraft Entity IDS and +// Minecraft Entity tags have been defined +type TargetSelectorEntityType interface { + Namespace() string + Name() string + String() string +} + +// TypeArg - defines a type argument for TargetSelectors +type TypeArg struct { + Type TargetSelectorEntityType + Not bool +} + +// WithType - Appends the type argument(s) to the current +// list of type arguments in the TargetSelector. Only for use with @e. +// Returns a new TargetSelector with the argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithType(args ...TypeArg) TargetSelector { + s.types = s.copyStringSlice(s.types) + for _, arg := range args { + if arg.Not { + s.types = append(s.types, "!"+arg.Type.String()) + } else { + s.types = append(s.types, arg.Type.String()) + } + } + return s +} + +// Tag - defines a tag argument for TargetSelectors. +// Empty Name corresponds to all entities with exactly 0 tags +type Tag struct { + Name string + Not bool +} + +// WithDataTag - Appends the tag argument(s) to the current +// list of tag arguments in the TargetSelector. +// Returns a new TargetSelector with the argument(s) added; +// original is left unchanged. Allows for method chaining +func (s TargetSelector) WithDataTag(tags ...Tag) TargetSelector { + s.tags = s.copyStringSlice(s.tags) + for _, tag := range tags { + if tag.Not { + s.tags = append(s.tags, "!"+tag.Name) + } else { + s.tags = append(s.tags, tag.Name) + } + } + return s +} + +// TODO: WithNBT - requires NBT implementation + +// TODO: WithAdvancements - requires achievement namespace id definitions + +// TODO: WithPredicates - requires predicate implementation