diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index f20de214..20369350 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1913,6 +1913,11 @@ Here is a list of built-in types, with their return value formats, as well as a * `'players'`: returning a list of valid player name string, logged in or not. If configured with `'single'` returns only one player or `null`. * `'intrange'`: same as `'floatrange'`, but requiring integers. * `'enchantment'`: name of an enchantment + * `'slot'`: provides a list of inventory type and slot. Can be configured with `'restrict'` requiring + `'player'`, `'enderchest'`, `'equipment'`, `'armor'`, `'weapon'`, `'container'`, `'villager'` or `'horse'` restricting selection of + available slots. Scarpet supports all vanilla slots, except for `horse.chest` - chest item, not items themselves. This you would + need to manage yourself via nbt directly. Also, for entities that change their capacity, like llamas, you need to check yourself if + the specified container slot is valid for your entity. * `'item'`: triple of item type, count of 1, and nbt. * `'message'`: text with expanded embedded player names * `'effect'`: string representing a status effect @@ -3586,29 +3591,30 @@ health back to the villager after being harmed. ## Manipulating inventories of blocks and entities Most functions in this category require inventory as the first argument. Inventory could be specified by an entity, -or a block, or position (three coordinates) of a potential block with inventory. Player enderchest inventory require -two arguments, keyword `'enderchest'`, followed by the player entity argument, or a single argument as a string of a -form: `'enderchest_steve'`. If your player name starts with enderchest, it can be always accessed by passing a player +or a block, or position (three coordinates) of a potential block with inventory, or can be preceded with inventory +type. +Inventory type can be `null` (default), `'enderchest'` denoting player enderchest storage, or `'equipment'` applying to +entities hand and armour pieces. Then the type can be followed by entity, block or position coordinates. +For instance, player enderchest inventory requires +two arguments, keyword `'enderchest'`, followed by the player entity argument, (or a single argument as a string of a +form: `'enderchest_steve'` for legacy support). If your player name starts with `'enderchest_'`, first of all, tough luck, +but then it can be always accessed by passing a player entity value. If all else fails, it will try to identify first three arguments as coordinates of a block position of -a block inventory. Player inventories can also be called by their name. +a block inventory. Player inventories can also be called by their name. -You can also use `'equipment'` as a keyword preceeding an entity indicating the need of access that entity as a mob. -Equipment inventory -consists of its armour and hand items, and a few living entites can have both, their regular inventory, and their equipment inventory. For some -entities (players) their regular inventory already contains the equipment. For entity types that only have -their equipment inventory, the equipment is returned by default. +A few living entities can have both: their regular inventory, and their equipment inventory. +Player's regular inventory already contains the equipment, but you can access the equipment part as well, as well as +their enderchest separately. For entity types that only have +their equipment inventory, the equipment is returned by default (`null` type). + +If that's confusing see examples under `inventory_size` on how to access inventories. All other `inventory_...()` functions +use the same scheme. -
-inventory_size(player()) => 41  // items
-inventory_size('enderchest', player()) => 27 // equipment
-inventory_size('equipment', player()) => 6 // equipment
-
If the entity or a block doesn't have -an inventory, they typically do nothing and return null. +an inventory, all API functions typically do nothing and return null. Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item. -Manipulating of the nbt data can be costly, but retrieving them from the tuple to match other aspects is cheap ### `stack_limit(item)` @@ -3681,14 +3687,26 @@ item name that serves as a replacement after crafting is done. Currently it can ### `inventory_size(inventory)` Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't -have an inventory +have an inventory.
 inventory_size(player()) => 41
 inventory_size('enderchest', player()) => 27 // enderchest
 inventory_size('equipment', player()) => 6 // equipment
+inventory_size(null, player()) => 41  // default inventory for players
+
 inventory_size(x,y,z) => 27 // chest
 inventory_size(block(pos)) => 5 // hopper
+
+horse = spawn('horse', x, y, z);
+inventory_size(horse); => 2 // default horse inventory
+inventory_size('equipment', horse); => 6 // unused horse equipment inventory
+inventory_size(null, horse); => 2 // default horse
+
+creeper = spawn('creeper', x, y, z);
+inventory_size(creeper); => 6 // default creeper inventory is equipment since it has no other
+inventory_size('equipment', creeper); => 6 // unused horse equipment inventory
+inventory_size(null, creeper); => 6 // creeper default is its equipment
 
### `inventory_has_items(inventory)` @@ -4157,8 +4175,83 @@ for the objective. ### `scoreboard_display(place, objective)` -sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, +Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, returns `null`. + +# Team + +### `team_list()`, `team_list(team)` + +Returns all available teams as a list with no arguments. + +When a `team` is specified, it returns all the players inside that team. If the `team` is invalid, returns `null`. + +### `team_add(team)`, `team_add(team,player)` + +With one argument, creates a new `team` and returns its name if successful, or `null` if team already exists. + + +`team_add('admin')` -> Create a team with the name 'admin' +`team_add('admin','Steve')` -> Joing the player 'Steve' into the team 'admin' + +If a `player` is specified, the player will join the given `team`. Returns `true` if player joined the team, or `false` if nothing changed since the player was already in this team. If the team is invalid, returns `null` + +### `team_remove(team)` + +Removes a `team`. Returns `true` if the team was deleted, or `null` if the team is invalid. + +### `team_leave(player)` + +Removes the `player` from the team he is in. Returns `true` if the player left a team, otherwise `false`. + +`team_leave('Steve')` -> Removes Steve from the team he is currently in +`for(team_list('admin'), team_leave('admin', _))` -> Remove all players from team 'admin' + +### `team_property(team,property,value?)` + +Reads the `property` of the `team` if no `value` is specified. If a `value` is added as a third argument, it sets the `property` to that `value`. + +* `collisionRule` + * Type: String + * Options: always, never, pushOtherTeams, pushOwnTeam + +* `color` + * Type: String + * Options: See [team command](https://minecraft.gamepedia.com/Commands/team#Arguments) (same strings as `'teamcolor'` [command argument](https://github.com/gnembon/fabric-carpet/blob/master/docs/scarpet/Full.md#command-argument-types] options)) + +* `displayName` + * Type: String or FormattedText, when querying returns FormattedText + +* `prefix` + * Type: String or FormattedText, when querying returns FormattedText + +* `suffix` + * Type: String or FormattedText, when querying returns FormattedText + +* `friendlyFire` + * Type: boolean + +* `seeFriendlyInvisibles` + * Type: boolean + +* `nametagVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +* `deathMessageVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +Examples: + +``` +team_property('admin','color','dark_red') Make the team color for team 'admin' dark red +team_property('admin','prefix',format('r Admin | ')) Set prefix of all players in 'admin' +team_property('admin','display_name','Administrators') Set display name for team 'admin' +team_property('admin','seeFriendlyInvisibles',true) Make all players in 'admin' see other admins even when invisible +team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible +``` + # Auxiliary aspects Collection of other methods that control smaller, yet still important aspects of the game diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md index e2a68f18..1ecc692b 100644 --- a/docs/scarpet/api/Inventories.md +++ b/docs/scarpet/api/Inventories.md @@ -3,29 +3,30 @@ ## Manipulating inventories of blocks and entities Most functions in this category require inventory as the first argument. Inventory could be specified by an entity, -or a block, or position (three coordinates) of a potential block with inventory. Player enderchest inventory require -two arguments, keyword `'enderchest'`, followed by the player entity argument, or a single argument as a string of a -form: `'enderchest_steve'`. If your player name starts with enderchest, it can be always accessed by passing a player +or a block, or position (three coordinates) of a potential block with inventory, or can be preceded with inventory +type. +Inventory type can be `null` (default), `'enderchest'` denoting player enderchest storage, or `'equipment'` applying to +entities hand and armour pieces. Then the type can be followed by entity, block or position coordinates. +For instance, player enderchest inventory requires +two arguments, keyword `'enderchest'`, followed by the player entity argument, (or a single argument as a string of a +form: `'enderchest_steve'` for legacy support). If your player name starts with `'enderchest_'`, first of all, tough luck, +but then it can be always accessed by passing a player entity value. If all else fails, it will try to identify first three arguments as coordinates of a block position of -a block inventory. Player inventories can also be called by their name. +a block inventory. Player inventories can also be called by their name. -You can also use `'equipment'` as a keyword preceeding an entity indicating the need of access that entity as a mob. -Equipment inventory -consists of its armour and hand items, and a few living entites can have both, their regular inventory, and their equipment inventory. For some -entities (players) their regular inventory already contains the equipment. For entity types that only have -their equipment inventory, the equipment is returned by default. +A few living entities can have both: their regular inventory, and their equipment inventory. +Player's regular inventory already contains the equipment, but you can access the equipment part as well, as well as +their enderchest separately. For entity types that only have +their equipment inventory, the equipment is returned by default (`null` type). + +If that's confusing see examples under `inventory_size` on how to access inventories. All other `inventory_...()` functions +use the same scheme. -
-inventory_size(player()) => 41  // items
-inventory_size('enderchest', player()) => 27 // equipment
-inventory_size('equipment', player()) => 6 // equipment
-
If the entity or a block doesn't have -an inventory, they typically do nothing and return null. +an inventory, all API functions typically do nothing and return null. Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item. -Manipulating of the nbt data can be costly, but retrieving them from the tuple to match other aspects is cheap ### `stack_limit(item)` @@ -98,14 +99,26 @@ item name that serves as a replacement after crafting is done. Currently it can ### `inventory_size(inventory)` Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't -have an inventory +have an inventory.
 inventory_size(player()) => 41
 inventory_size('enderchest', player()) => 27 // enderchest
 inventory_size('equipment', player()) => 6 // equipment
+inventory_size(null, player()) => 41  // default inventory for players
+
 inventory_size(x,y,z) => 27 // chest
 inventory_size(block(pos)) => 5 // hopper
+
+horse = spawn('horse', x, y, z);
+inventory_size(horse); => 2 // default horse inventory
+inventory_size('equipment', horse); => 6 // unused horse equipment inventory
+inventory_size(null, horse); => 2 // default horse
+
+creeper = spawn('creeper', x, y, z);
+inventory_size(creeper); => 6 // default creeper inventory is equipment since it has no other
+inventory_size('equipment', creeper); => 6 // unused horse equipment inventory
+inventory_size(null, creeper); => 6 // creeper default is its equipment
 
### `inventory_has_items(inventory)` diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 3e6ddbdf..9e9fc5bd 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -214,6 +214,11 @@ Here is a list of built-in types, with their return value formats, as well as a * `'players'`: returning a list of valid player name string, logged in or not. If configured with `'single'` returns only one player or `null`. * `'intrange'`: same as `'floatrange'`, but requiring integers. * `'enchantment'`: name of an enchantment + * `'slot'`: provides a list of inventory type and slot. Can be configured with `'restrict'` requiring + `'player'`, `'enderchest'`, `'equipment'`, `'armor'`, `'weapon'`, `'container'`, `'villager'` or `'horse'` restricting selection of + available slots. Scarpet supports all vanilla slots, except for `horse.chest` - chest item, not items themselves. This you would + need to manage yourself via nbt directly. Also, for entities that change their capacity, like llamas, you need to check yourself if + the specified container slot is valid for your entity. * `'item'`: triple of item type, count of 1, and nbt. * `'message'`: text with expanded embedded player names * `'effect'`: string representing a status effect diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index d2feb12d..0be72203 100644 --- a/docs/scarpet/api/Scoreboard.md +++ b/docs/scarpet/api/Scoreboard.md @@ -27,5 +27,80 @@ for the objective. ### `scoreboard_display(place, objective)` -sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, +Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, returns `null`. + +# Team + +### `team_list()`, `team_list(team)` + +Returns all available teams as a list with no arguments. + +When a `team` is specified, it returns all the players inside that team. If the `team` is invalid, returns `null`. + +### `team_add(team)`, `team_add(team,player)` + +With one argument, creates a new `team` and returns its name if successful, or `null` if team already exists. + + +`team_add('admin')` -> Create a team with the name 'admin' +`team_add('admin','Steve')` -> Joing the player 'Steve' into the team 'admin' + +If a `player` is specified, the player will join the given `team`. Returns `true` if player joined the team, or `false` if nothing changed since the player was already in this team. If the team is invalid, returns `null` + +### `team_remove(team)` + +Removes a `team`. Returns `true` if the team was deleted, or `null` if the team is invalid. + +### `team_leave(player)` + +Removes the `player` from the team he is in. Returns `true` if the player left a team, otherwise `false`. + +`team_leave('Steve')` -> Removes Steve from the team he is currently in +`for(team_list('admin'), team_leave('admin', _))` -> Remove all players from team 'admin' + +### `team_property(team,property,value?)` + +Reads the `property` of the `team` if no `value` is specified. If a `value` is added as a third argument, it sets the `property` to that `value`. + +* `collisionRule` + * Type: String + * Options: always, never, pushOtherTeams, pushOwnTeam + +* `color` + * Type: String + * Options: See [team command](https://minecraft.gamepedia.com/Commands/team#Arguments) (same strings as `'teamcolor'` [command argument](https://github.com/gnembon/fabric-carpet/blob/master/docs/scarpet/Full.md#command-argument-types] options)) + +* `displayName` + * Type: String or FormattedText, when querying returns FormattedText + +* `prefix` + * Type: String or FormattedText, when querying returns FormattedText + +* `suffix` + * Type: String or FormattedText, when querying returns FormattedText + +* `friendlyFire` + * Type: boolean + +* `seeFriendlyInvisibles` + * Type: boolean + +* `nametagVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +* `deathMessageVisibility` + * Type: String + * Options: always, never, hideForOtherTeams, hideForOwnTeam + +Examples: + +``` +team_property('admin','color','dark_red') Make the team color for team 'admin' dark red +team_property('admin','prefix',format('r Admin | ')) Set prefix of all players in 'admin' +team_property('admin','display_name','Administrators') Set display name for team 'admin' +team_property('admin','seeFriendlyInvisibles',true) Make all players in 'admin' see other admins even when invisible +team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible +``` + diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 012ad76f..db4d0d7b 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -84,6 +84,11 @@ scoreboard scoreboard_add scoreboard_remove scoreboard_display +team_add +team_list +team_remove +team_leave +team_property see_sky set set_biome diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index cad62780..3ad710dc 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -60,6 +60,8 @@ scoreboard scoreboard_add scoreboard_remove scoreboard_display + team_add team_list team_remove team_leave team_property + player spawn entity_id entity_list entity_area entity_selector query modify entity_event entity_load_handler entity_types stack_limit item_category recipe_data crafting_remaining_item diff --git a/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml b/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml index ffb50e58..a24fe8f0 100644 --- a/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml +++ b/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml @@ -60,6 +60,8 @@ scoreboard scoreboard_add scoreboard_remove scoreboard_display + team_add team_list team_remove team_leave team_property + player spawn entity_id entity_list entity_area entity_selector query modify entity_event stack_limit item_category recipe_data crafting_remaining_item diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index 881bfd26..af653369 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -9,14 +9,20 @@ import carpet.script.value.ListValue; import carpet.script.value.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; +import carpet.script.value.FormattedTextValue; import carpet.script.value.Value; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.scoreboard.ServerScoreboard; import net.minecraft.scoreboard.ScoreboardCriterion; import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.scoreboard.ScoreboardPlayerScore; +import net.minecraft.scoreboard.Team; +import net.minecraft.scoreboard.AbstractTeam; import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import java.util.stream.Collectors; @@ -147,5 +153,249 @@ public class Scoreboards { scoreboard.setObjectiveSlot(slot, objective); return (_c, _t) -> new NumericValue(slot); }); + + expression.addLazyFunction("team_list", -1, (c, t, lv) -> + { + if(lv.size() > 1) throw new InternalExpressionException("'team_list' requires zero or one parameters"); + CarpetContext cc = (CarpetContext)c; + ServerScoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + if(lv.size() == 0) { + Value ret = ListValue.wrap(scoreboard.getTeamNames().stream().map(StringValue::of).collect(Collectors.toList())); + return (_c, _t) -> ret; + } else if(lv.size() == 1) { + Team team = scoreboard.getTeam(lv.get(0).evalValue(c).getString()); + if(team == null) return LazyValue.NULL; + Value ret = ListValue.wrap(team.getPlayerList().stream().map(StringValue::of).collect(Collectors.toList())); + return (_c, _t) -> ret; + } else { + return LazyValue.NULL; + } + }); + + + expression.addLazyFunction("team_add", -1, (c, t, lv) -> + { + if(!(lv.size() < 3 && lv.size() > 0)) throw new InternalExpressionException("'team_add' requires one or two parameters"); + + CarpetContext cc = (CarpetContext)c; + ServerScoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + String teamName = lv.get(0).evalValue(c).getString(); + + if(lv.size() == 1) { + if(scoreboard.getTeam(teamName) == null) { + scoreboard.addTeam(teamName); + Value ret = new StringValue(teamName); + return (_c, _t) -> ret; + } else { + return LazyValue.NULL; + } + } else if(lv.size() == 2) { + Value playerVal = lv.get(1).evalValue(c); + + String player = EntityValue.getPlayerNameByValue(playerVal); + if(player == null) return LazyValue.NULL; + + Team team = scoreboard.getTeam(teamName); + if(team == null) return LazyValue.NULL; + + if(team.isEqual(scoreboard.getPlayerTeam(player))) { + return LazyValue.FALSE; + } else { + scoreboard.addPlayerToTeam(player,scoreboard.getTeam(teamName)); + return LazyValue.TRUE; + } + } else { + return LazyValue.NULL; + } + }); + + expression.addLazyFunction("team_remove", 1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerScoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + Value teamVal = lv.get(0).evalValue(c); + + String team = teamVal.getString(); + + if(scoreboard.getTeam(team) != null) { + scoreboard.removeTeam(scoreboard.getTeam(team)); + return LazyValue.TRUE; + } else { + return LazyValue.NULL; + } + }); + + + expression.addLazyFunction("team_leave", 1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerScoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + Value playerVal = lv.get(0).evalValue(c); + + String player = EntityValue.getPlayerNameByValue(playerVal); + if(player == null) return LazyValue.NULL; + + Value ret = new NumericValue(scoreboard.clearPlayerTeam(player)); + return (_c, _t) -> ret; + }); + + expression.addLazyFunction("team_property", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerScoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + + if(lv.size() < 2 || lv.size() > 3) throw new InternalExpressionException("'team_property' requires two or three arguments"); + + Value teamVal = lv.get(0).evalValue(c); + Value propertyVal = lv.get(1).evalValue(c); + + Value settingVal = null; + boolean modifying = false; + if(lv.size() == 3) { + modifying = true; + settingVal = lv.get(2).evalValue(c); + } + + Team team = scoreboard.getTeam(teamVal.getString()); + if(team == null) return LazyValue.NULL; + + if(!(propertyVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string as the second argument"); + + switch (propertyVal.getString()) { + case "collision_rule": + if(!modifying) { + Value ret = new StringValue(team.getCollisionRule().name); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string as the third argument for the property " + propertyVal.getString()); + + AbstractTeam.CollisionRule collisionRule = AbstractTeam.CollisionRule.getRule(settingVal.getString()); + if(collisionRule == null) throw new InternalExpressionException("Unknown value for property " + propertyVal.getString() + ": " + settingVal.getString()); + team.setCollisionRule(collisionRule); + break; + case "color": + if(!modifying) { + Value ret = new StringValue(team.getColor().getName()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string as the third argument for the property " + propertyVal.getString()); + + Formatting color = Formatting.byName(settingVal.getString().toUpperCase()); + if(color == null || !color.isColor()) throw new InternalExpressionException("Unknown value for property " + propertyVal.getString() + ": " + settingVal.getString()); + team.setColor(color); + + break; + case "death_message_visibility": + if(!modifying) { + Value ret = new StringValue(team.getDeathMessageVisibilityRule().name); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string as the third argument for the property " + propertyVal.getString()); + + AbstractTeam.VisibilityRule deathMessageVisibility = AbstractTeam.VisibilityRule.getRule(settingVal.getString()); + if(deathMessageVisibility == null) throw new InternalExpressionException("Unknown value for property " + propertyVal.getString() + ": " + settingVal.getString()); + team.setDeathMessageVisibilityRule(deathMessageVisibility); + + break; + case "display_name": + if(!modifying) { + Value ret = new FormattedTextValue(team.getDisplayName()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string or formatted text as the third argument for the property " + propertyVal.getString()); + + Text displayName; + + if(settingVal instanceof FormattedTextValue) { + displayName = ((FormattedTextValue) settingVal).getText(); + } else { + displayName = new LiteralText(settingVal.getString()); + } + + team.setDisplayName(displayName); + + break; + case "friendly_fire": + if(!modifying) { + Value ret = new NumericValue(team.isFriendlyFireAllowed()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof NumericValue)) throw new InternalExpressionException("'team_property' requires a boolean as the third argument for the property " + propertyVal.getString()); + + boolean friendlyFire = settingVal.getBoolean(); + team.setFriendlyFireAllowed(friendlyFire); + break; + case "nametag_visibility": + if(!modifying) { + Value ret = new StringValue(team.getNameTagVisibilityRule().name); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string as the third argument for the property " + propertyVal.getString()); + + AbstractTeam.VisibilityRule nametagVisibility = AbstractTeam.VisibilityRule.getRule(settingVal.getString()); + if(nametagVisibility == null) throw new InternalExpressionException("Unknown value for property " + propertyVal.getString() + ": " + settingVal.getString()); + team.setNameTagVisibilityRule(nametagVisibility); + + break; + case "prefix": + if(!modifying) { + Value ret = new FormattedTextValue(team.getPrefix()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property ' requires a string or formatted text as the third argument for the property " + propertyVal.getString()); + + Text prefix; + + if(settingVal instanceof FormattedTextValue) { + prefix = ((FormattedTextValue) settingVal).getText(); + } else { + prefix = new LiteralText(settingVal.getString()); + } + + team.setPrefix(prefix); + break; + case "see_friendly_invisibles": + if(!modifying) { + Value ret = new NumericValue(team.shouldShowFriendlyInvisibles()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof NumericValue)) throw new InternalExpressionException("'team_property' requires a boolean as the third argument for the property " + propertyVal.getString()); + + boolean seeFriendlyInvisibles = settingVal.getBoolean(); + team.setShowFriendlyInvisibles(seeFriendlyInvisibles); + + break; + case "suffix": + if(!modifying) { + Value ret = new FormattedTextValue(team.getSuffix()); + return (_c, _t) -> ret; + } + + if(!(settingVal instanceof StringValue)) throw new InternalExpressionException("'team_property' requires a string or formatted text as the third argument for the property " + propertyVal.getString()); + + Text suffix; + + if(settingVal instanceof FormattedTextValue) { + suffix = ((FormattedTextValue) settingVal).getText(); + } else { + suffix = new LiteralText(settingVal.getString()); + } + + team.setSuffix(suffix); + break; + default: + throw new InternalExpressionException("team property " + propertyVal.getString() + " is not a valid property"); + } + return LazyValue.TRUE; + }); } } + diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java index 347c8017..2327d93f 100644 --- a/src/main/java/carpet/script/command/CommandArgument.java +++ b/src/main/java/carpet/script/command/CommandArgument.java @@ -47,6 +47,7 @@ import net.minecraft.command.argument.EntitySummonArgumentType; import net.minecraft.command.argument.GameProfileArgumentType; import net.minecraft.command.argument.IdentifierArgumentType; import net.minecraft.command.argument.ItemEnchantmentArgumentType; +import net.minecraft.command.argument.ItemSlotArgumentType; import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.command.argument.MessageArgumentType; import net.minecraft.command.argument.MobEffectArgumentType; @@ -78,11 +79,14 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec2f; import net.minecraft.util.registry.Registry; +import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -158,6 +162,7 @@ public abstract class CommandArgument ), // item_predicate ?? //same as item but accepts tags, not sure right now // slot // item_slot + new SlotArgument(), new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack, (c, p) -> ValueConversions.of(ItemStackArgumentType.getItemStackArgument(c, p).createStack(1, false)), false ), @@ -849,6 +854,100 @@ public abstract class CommandArgument return IntArgument::new; } } + private static class SlotArgument extends CommandArgument + { + private String restrict; + private static final Map, Set>> RESTRICTED_CONTAINERS = new HashMap, Set>>(){{ + int i; + for (String source : Arrays.asList("player", "enderchest", "equipment", "armor", "weapon", "container", "villager", "horse")) + put(source, Pair.of(new HashSet<>(), new HashSet<>())); + for (i = 0; i < 41; i++) get("player").getLeft().add(i); + for(i = 0; i < 41; i++) get("player").getRight().add("container." + i); + for(i = 0; i < 9; i++) get("player").getRight().add("hotbar." + i); + for(i = 0; i < 27; i++) get("player").getRight().add("inventory." + i); + for (String place : Arrays.asList("weapon", "weapon.mainhand", "weapon.offhand")) + { + get("player").getRight().add(place); + get("equipment").getRight().add(place); + get("weapon").getRight().add(place); + } + for (String place : Arrays.asList("armor.feet","armor.legs", "armor.chest","armor.head")) + { + get("player").getRight().add(place); + get("equipment").getRight().add(place); + get("armor").getRight().add(place); + } + + for (i = 0; i < 27; i++) get("enderchest").getLeft().add(200+i); + for(i = 0; i < 27; i++) get("enderchest").getRight().add("enderchest." + i); + + for (i = 0; i < 6; i++) get("equipment").getLeft().add(98+i); + + for (i = 0; i < 4; i++) get("armor").getLeft().add(100+i); + + for (i = 0; i < 2; i++) get("weapon").getLeft().add(98+i); + + for (i = 0; i < 54; i++) get("container").getLeft().add(i); + for(i = 0; i < 41; i++) get("container").getRight().add("container." + i); + + for (i = 0; i < 8; i++) get("villager").getLeft().add(i); + for(i = 0; i < 8; i++) get("villager").getRight().add("villager." + i); + + for (i = 0; i < 15; i++) get("horse").getLeft().add(500+i); + for(i = 0; i < 15; i++) get("horse").getRight().add("horse." + i); + get("horse").getLeft().add(400); + get("horse").getRight().add("horse.saddle"); + get("horse").getLeft().add(401); + get("horse").getRight().add("horse.armor"); + }}; + + protected SlotArgument() + { + super("slot", ItemSlotArgumentType.itemSlot().getExamples(), false); + } + + @Override + protected ArgumentType getArgumentType() throws CommandSyntaxException + { + return ItemSlotArgumentType.itemSlot(); + } + + @Override + protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException + { + int slot = ItemSlotArgumentType.getItemSlot(context, param); + if (restrict != null && !RESTRICTED_CONTAINERS.get(restrict).getLeft().contains(slot)) + { + throw new SimpleCommandExceptionType(new LiteralText("Incorrect slot restricted to "+restrict)).create(); + } + return ValueConversions.ofVanillaSlotResult(slot); + } + + @Override + protected void configure(Map config, CarpetScriptHost host) + { + super.configure(config, host); + if (config.containsKey("restrict")) + { + restrict = config.get("restrict").getString().toLowerCase(Locale.ROOT); + needsMatching = true; + if (!RESTRICTED_CONTAINERS.containsKey(restrict)) + throw new InternalExpressionException("Incorrect slot restriction: "+restrict); + } + } + + @Override + protected Collection getOptions(CommandContext context, CarpetScriptHost host) throws CommandSyntaxException + { + return restrict==null?super.getOptions(context, host):RESTRICTED_CONTAINERS.get(restrict).getRight(); + } + + @Override + protected Supplier factory() + { + return SlotArgument::new; + } + } @FunctionalInterface private interface ValueExtractor diff --git a/src/main/java/carpet/script/utils/EquipmentInventory.java b/src/main/java/carpet/script/utils/EquipmentInventory.java new file mode 100644 index 00000000..723157c5 --- /dev/null +++ b/src/main/java/carpet/script/utils/EquipmentInventory.java @@ -0,0 +1,126 @@ +package carpet.script.utils; + +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; + +import java.util.Arrays; +import java.util.List; + +public class EquipmentInventory implements Inventory +{ + private static final List slotToSlot = Arrays.asList( + EquipmentSlot.MAINHAND, + EquipmentSlot.FEET, EquipmentSlot.LEGS, EquipmentSlot.CHEST, EquipmentSlot.HEAD, + EquipmentSlot.OFFHAND + ); + + LivingEntity mob; + public EquipmentInventory(LivingEntity mob) + { + this.mob = mob; + } + + @Override + public int size() + { + return 6; + } + + @Override + public boolean isEmpty() + { + for (EquipmentSlot slot: slotToSlot) + if (!mob.getEquippedStack(slot).isEmpty()) return false; + return true; + } + + @Override + public ItemStack getStack(int slot) + { + EquipmentSlot slotSlot; + try + { + slotSlot = slotToSlot.get(slot); + } + catch (IndexOutOfBoundsException ignored) + { + //going out of the index should be really exceptional + return null; + } + return mob.getEquippedStack(slotSlot); + } + + @Override + public ItemStack removeStack(int slot, int amount) + { + EquipmentSlot slotSlot; + try + { + slotSlot = slotToSlot.get(slot); + } + catch (IndexOutOfBoundsException ignored) + { + //going out of the index should be really exceptional + return null; + } + ItemStack stack = mob.getEquippedStack(slotSlot); + stack.decrement(amount); + return stack; + } + + @Override + public ItemStack removeStack(int slot) + { + EquipmentSlot slotSlot; + try + { + slotSlot = slotToSlot.get(slot); + } + catch (IndexOutOfBoundsException ignored) + { + //going out of the index should be really exceptional + return null; + } + ItemStack previous = mob.getEquippedStack(slotSlot); + mob.equipStack(slotSlot, ItemStack.EMPTY); + return previous; + } + + @Override + public void setStack(int slot, ItemStack stack) + { + EquipmentSlot slotSlot; + try + { + slotSlot = slotToSlot.get(slot); + } + catch (IndexOutOfBoundsException ignored) + { + //going out of the index should be really exceptional + return; + } + mob.equipStack(slotSlot, stack); + } + + @Override + public void markDirty() + { + + } + + @Override + public boolean canPlayerUse(PlayerEntity player) + { + return false; + } + + @Override + public void clear() + { + for (EquipmentSlot slot: slotToSlot) + mob.equipStack(slot, ItemStack.EMPTY); + } +} diff --git a/src/main/java/carpet/script/utils/LivingInventory.java b/src/main/java/carpet/script/utils/LivingInventory.java deleted file mode 100644 index 34c563cd..00000000 --- a/src/main/java/carpet/script/utils/LivingInventory.java +++ /dev/null @@ -1,96 +0,0 @@ -package carpet.script.utils; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.inventory.Inventory; -import net.minecraft.item.ItemStack; - -public class LivingInventory implements Inventory -{ - private static final Int2ObjectMap slotToSlot = new Int2ObjectOpenHashMap(){{ - put(0, EquipmentSlot.MAINHAND); - put(1, EquipmentSlot.FEET); - put(2, EquipmentSlot.LEGS); - put(3, EquipmentSlot.CHEST); - put(4, EquipmentSlot.HEAD); - put(5, EquipmentSlot.OFFHAND); - }}; - - LivingEntity mob; - public LivingInventory(LivingEntity mob) - { - this.mob = mob; - } - - @Override - public int size() - { - return 6; - } - - @Override - public boolean isEmpty() - { - for (EquipmentSlot slot: EquipmentSlot.values()) - if (!mob.getEquippedStack(slot).isEmpty()) return false; - return true; - } - - @Override - public ItemStack getStack(int slot) - { - EquipmentSlot slotSlot = slotToSlot.getOrDefault(slot, null); - if (slotSlot == null) return null; - return mob.getEquippedStack(slotToSlot.get(slot)); - } - - @Override - public ItemStack removeStack(int slot, int amount) - { - EquipmentSlot slotSlot = slotToSlot.getOrDefault(slot, null); - if (slotSlot == null) return null; - ItemStack stack = mob.getEquippedStack(slotSlot); - stack.decrement(amount); - return stack; - } - - @Override - public ItemStack removeStack(int slot) - { - EquipmentSlot slotSlot = slotToSlot.getOrDefault(slot, null); - if (slotSlot == null) return null; - ItemStack previous = mob.getEquippedStack(slotSlot); - mob.equipStack(slotSlot, ItemStack.EMPTY); - return previous; - } - - @Override - public void setStack(int slot, ItemStack stack) - { - EquipmentSlot slotSlot = slotToSlot.getOrDefault(slot, null); - if (slotSlot == null) return; - mob.equipStack(slotSlot, stack); - } - - @Override - public void markDirty() - { - - } - - @Override - public boolean canPlayerUse(PlayerEntity player) - { - return false; - } - - @Override - public void clear() - { - for (EquipmentSlot slot: EquipmentSlot.values()) - mob.equipStack(slot, ItemStack.EMPTY); - } -} diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 9b003d7e..0fedc0bc 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -144,6 +144,28 @@ public class EntityValue extends Value return player; } + public static String getPlayerNameByValue(Value value) + { + String playerName = null; + if (value instanceof EntityValue) + { + Entity e = ((EntityValue) value).getEntity(); + if (e instanceof ServerPlayerEntity) + { + playerName = e.getEntityName(); + } + } + else if (value.isNull()) + { + return null; + } + else + { + playerName = value.getString(); + } + return playerName; + } + @Override public String getString() { diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index a69ae32d..f6be3d11 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -4,7 +4,7 @@ import carpet.fakes.InventoryBearerInterface; import carpet.script.CarpetContext; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; -import carpet.script.utils.LivingInventory; +import carpet.script.utils.EquipmentInventory; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.Block; @@ -175,47 +175,15 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return inventory; } - public static InventoryLocator locateInventory(CarpetContext c, List params, int offset) { try { - Inventory inv = null; - Value v1 = params.get(0 + offset).evalValue(c); - if (v1 instanceof EntityValue) + Value v1 = params.get(offset).evalValue(c); + if (v1.isNull()) { - Entity e = ((EntityValue) v1).getEntity(); - if (e instanceof PlayerEntity) inv = ((PlayerEntity) e).getInventory(); - else if (e instanceof Inventory) inv = (Inventory) e; - else if (e instanceof VillagerEntity) inv = ((VillagerEntity) e).getInventory(); - else if (e instanceof InventoryBearerInterface) inv = ((InventoryBearerInterface)e).getCMInventory(); - else if (e instanceof LivingEntity) inv = new LivingInventory((MobEntity) e); - if (inv == null) - return null; - - return new InventoryLocator(e, e.getBlockPos(), inv, offset + 1); - } - else if (v1 instanceof BlockValue) - { - BlockPos pos = ((BlockValue) v1).getPos(); - if (pos == null) - throw new InternalExpressionException("Block to access inventory needs to be positioned in the world"); - inv = getInventoryAt(c.s.getWorld(), pos); - if (inv == null) - return null; - return new InventoryLocator(pos, pos, inv, offset + 1); - } - else if (v1 instanceof ListValue) - { - List args = ((ListValue) v1).getItems(); - BlockPos pos = new BlockPos( - NumericValue.asNumber(args.get(0)).getDouble(), - NumericValue.asNumber(args.get(1)).getDouble(), - NumericValue.asNumber(args.get(2)).getDouble()); - inv = getInventoryAt(c.s.getWorld(), pos); - if (inv == null) - return null; - return new InventoryLocator(pos, pos, inv, offset + 1); + offset ++; + v1 = params.get(offset).evalValue(c); } else if (v1 instanceof StringValue) { @@ -227,34 +195,67 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa if (player == null) throw new InternalExpressionException("enderchest inventory requires player argument"); return new InventoryLocator(player, player.getBlockPos(), player.getEnderChestInventory(), offset + 2, true); } - else if (strVal.equals("equipment")) + if (strVal.equals("equipment")) { Value v2 = params.get(1 + offset).evalValue(c); - if (!(v2 instanceof EntityValue)) throw new InternalExpressionException("mob inventory requires a mob argument"); + if (!(v2 instanceof EntityValue)) throw new InternalExpressionException("Equipment inventory requires a living entity argument"); Entity e = ((EntityValue) v2).getEntity(); - if (!(e instanceof LivingEntity)) throw new InternalExpressionException("mob inventory requires a mob argument"); - return new InventoryLocator(e, e.getBlockPos(), new LivingInventory((LivingEntity) e), offset + 2, false); - } - else - { - boolean isEnder = strVal.startsWith("enderchest_"); - if (isEnder) strVal = strVal.substring(11); // len("enderchest_") - ServerPlayerEntity player = c.s.getMinecraftServer().getPlayerManager().getPlayer(strVal); - if (player == null) throw new InternalExpressionException("String description of an inventory should either denote a player or player's enderchest"); - return new InventoryLocator( - player, - player.getBlockPos(), - isEnder ? player.getEnderChestInventory() : player.getInventory(), - offset + 1, - isEnder - ); + if (!(e instanceof LivingEntity)) throw new InternalExpressionException("Equipment inventory requires a living entity argument"); + return new InventoryLocator(e, e.getBlockPos(), new EquipmentInventory((LivingEntity) e), offset + 2); } + boolean isEnder = strVal.startsWith("enderchest_"); + if (isEnder) strVal = strVal.substring(11); // len("enderchest_") + ServerPlayerEntity player = c.s.getMinecraftServer().getPlayerManager().getPlayer(strVal); + if (player == null) throw new InternalExpressionException("String description of an inventory should either denote a player or player's enderchest"); + return new InventoryLocator( + player, + player.getBlockPos(), + isEnder ? player.getEnderChestInventory() : player.inventory, + offset + 1, + isEnder + ); + } + if (v1 instanceof EntityValue) + { + Inventory inv = null; + Entity e = ((EntityValue) v1).getEntity(); + if (e instanceof PlayerEntity) inv = ((PlayerEntity) e).getInventory(); + else if (e instanceof Inventory) inv = (Inventory) e; + else if (e instanceof VillagerEntity) inv = ((VillagerEntity) e).getInventory(); + else if (e instanceof InventoryBearerInterface) inv = ((InventoryBearerInterface)e).getCMInventory(); + else if (e instanceof LivingEntity) return new InventoryLocator(e, e.getBlockPos(), new EquipmentInventory((MobEntity) e), offset+1); + if (inv == null) + return null; + + return new InventoryLocator(e, e.getBlockPos(), inv, offset+1); + } + if (v1 instanceof BlockValue) + { + BlockPos pos = ((BlockValue) v1).getPos(); + if (pos == null) + throw new InternalExpressionException("Block to access inventory needs to be positioned in the world"); + Inventory inv = getInventoryAt(c.s.getWorld(), pos); + if (inv == null) + return null; + return new InventoryLocator(pos, pos, inv, offset+1); + } + if (v1 instanceof ListValue) + { + List args = ((ListValue) v1).getItems(); + BlockPos pos = new BlockPos( + NumericValue.asNumber(args.get(0)).getDouble(), + NumericValue.asNumber(args.get(1)).getDouble(), + NumericValue.asNumber(args.get(2)).getDouble()); + Inventory inv = getInventoryAt(c.s.getWorld(), pos); + if (inv == null) + return null; + return new InventoryLocator(pos, pos, inv, offset+1); } BlockPos pos = new BlockPos( NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(), NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble()); - inv = getInventoryAt(c.s.getWorld(), pos); + Inventory inv = getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 3); diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index 0a229680..9fa06397 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -330,4 +330,49 @@ public class ValueConversions } throw new InternalExpressionException("Unknown property type: "+p.getName()); } + + + private static final Map slotIdsToSlotParams = new HashMap() {{ + int n; + //covers blocks, player hotbar and inventory, and all default inventories + for(n = 0; n < 54; ++n) { + put(n, ListValue.of(Value.NULL, NumericValue.of(n))); + } + for(n = 0; n < 27; ++n) { + put(200+n, ListValue.of(StringValue.of("enderchest"), NumericValue.of(n))); + } + + // villager + for(n = 0; n < 8; ++n) { + put(300+n, ListValue.of(Value.NULL, NumericValue.of(n))); + } + + // horse, llamas, donkeys, etc. + // two first slots are for saddle and armour + for(n = 0; n < 15; ++n) { + put(500+n, ListValue.of(Value.NULL, NumericValue.of(n+2))); + } + Value equipment = StringValue.of("equipment"); + // weapon main hand + put(98, ListValue.of(equipment, NumericValue.of(0))); + // offhand + put(99, ListValue.of(equipment, NumericValue.of(5))); + // feet, legs, chest, head + for(n = 0; n < 4; ++n) { + put(100+n, ListValue.of(equipment, NumericValue.of(n+1))); + } + //horse defaults saddle + put(400, ListValue.of(Value.NULL, NumericValue.of(0))); + // armor + put(401, ListValue.of(Value.NULL, NumericValue.of(1))); + // chest itself on the donkey is wierd - use NBT to alter that. + //hashMap.put("horse.chest", 499); + }}; + + public static Value ofVanillaSlotResult(int itemSlot) + { + Value ret = slotIdsToSlotParams.get(itemSlot); + if (ret == null) return ListValue.of(Value.NULL, NumericValue.of(itemSlot)); + return ret; + } }