Origins: Powerful Powers
» CookbookPage 17 of 17

Cookbook

Complete powers that combine several OPP features. Each recipe lists its files. Put them in data/mypack/powers/ and add the powers to an origin.

RecipeUses
Ragevariables, variable_bar, recent_damage_taken, modifier resource
Combo countervariables, bi-entity execute, expression, with_variables
Team bankvariables, Scoreboard.get, with_variables, /opp with
Loud footstepsis_moving_loudly, vibration_on_cooldown, set_vibration_cooldown
Radiostart_broadcast_voice, stop_broadcast_voice, is_broadcasting_voice
Linked inventoryItem.id, Item.count, list, for_each, for_each_read, with_variables, /opp powerinv copy
BondGlobal.set, Global.bind, Global.has, bi-entity expression, variables

Rage

Taking damage builds rage (up to 100). Rage fades between hits, and every 20 rage adds 1 damage to your attacks.

rage.json:

json
{
  "type": "origins-powerful-powers:variables",
  "fields": [
    "int rage = 0;",
    "int rageDamage = 0;   // rage / 20, for the damage modifier"
  ]
}

rage_bar.json: the visible bar.

json
{
  "type": "origins-powerful-powers:variable_bar",
  "variable": "rage",
  "min": 0,
  "max": 100,
  "hud_render": {
    "should_render": true,
    "bar_index": 2
  }
}

rage_damage_bar.json: a hidden bar for the modifier to read rageDamage.

json
{
  "type": "origins-powerful-powers:variable_bar",
  "variable": "rageDamage",
  "min": 0,
  "max": 5,
  "hud_render": {
    "should_render": false
  }
}

rage_tick.json: gain rage while being hurt, and lose it between hits.

json
{
  "type": "origins:action_over_time",
  "interval": 20,
  "entity_action": {
    "type": "origins:if_else",
    "condition": {
      "type": "origins-powerful-powers:recent_damage_taken",
      "window_ticks": 20,
      "comparison": ">",
      "compare_to": 0
    },
    "if_action": {
      "type": "origins-powerful-powers:execute",
      "code": [
        "rage = Math.min(rage + 10, 100);",
        "rageDamage = rage / 20;"
      ]
    },
    "else_action": {
      "type": "origins-powerful-powers:execute",
      "code": [
        "rage = Math.max(rage - 5, 0);",
        "rageDamage = rage / 20;"
      ]
    }
  }
}

rage_damage.json: the bonus damage.

json
{
  "type": "origins:modify_damage_dealt",
  "modifier": {
    "operation": "add_base_early",
    "value": 0,
    "resource": "mypack:rage_damage_bar"
  }
}

How it fits together:

  • recent_damage_taken with a 20-tick window is "was I hurt in the last second".
  • The code keeps rageDamage in step with rage, using whole-number division.
  • The modifier reads rageDamage through the hidden bar (page 9).

Combo counter

Each hit within 2 seconds of the last one adds to a combo. At 5 or more, your hits deal 25% more. The action bar shows the count.

combo.json:

json
{
  "type": "origins-powerful-powers:variables",
  "fields": [
    "int combo = 0;",
    "int comboTimer = 0;"
  ]
}

combo_hit.json: count hits.

json
{
  "type": "origins:action_on_hit",
  "bientity_action": {
    "type": "origins-powerful-powers:execute",
    "code": "combo++; comboTimer = 40;"
  }
}

combo_tick.json: the timer, and the action bar.

json
{
  "type": "origins:action_over_time",
  "interval": 1,
  "entity_action": {
    "type": "origins:and",
    "actions": [
      {
        "type": "origins-powerful-powers:execute",
        "code": [
          "comboTimer = Math.max(comboTimer - 1, 0);",
          "combo = comboTimer == 0 ? 0 : combo;"
        ]
      },
      {
        "type": "origins:if_else",
        "condition": {
          "type": "origins-powerful-powers:expression",
          "expression": "combo > 0"
        },
        "if_action": {
          "type": "origins-powerful-powers:with_variables",
          "action": {
            "type": "origins:execute_command",
            "command": "title @s actionbar {\"text\":\"Combo x${combo}\",\"color\":\"${combo >= 5 ? \"gold\" : \"white\"}\"}"
          }
        }
      }
    ]
  }
}

combo_bonus.json:

json
{
  "type": "origins:modify_damage_dealt",
  "condition": {
    "type": "origins-powerful-powers:expression",
    "expression": "combo >= 5"
  },
  "modifier": {
    "operation": "multiply_total_multiplicative",
    "value": 0.25
  }
}

In the bi-entity action in combo_hit.json, a bare combo is the actor's, which is you (page 8). The target needs no variables.


Team bank

Everyone with the power has their own coins, and can deposit into one shared bank kept on a scoreboard.

load.mcfunction (run on load):

mcfunction
scoreboard objectives add team_bank dummy

wallet.json:

json
{
  "type": "origins-powerful-powers:variables",
  "fields": [
    "long coins = 100;"
  ]
}

deposit.json: press your primary key to deposit up to 10 coins.

json
{
  "type": "origins:active_self",
  "key": {
    "key": "key.origins.primary_active"
  },
  "condition": {
    "type": "origins-powerful-powers:expression",
    "expression": "coins > 0"
  },
  "entity_action": {
    "type": "origins:and",
    "actions": [
      {
        "type": "origins-powerful-powers:with_variables",
        "action": {
          "type": "origins:execute_command",
          "command": "scoreboard players add #bank team_bank ${Math.min(coins, 10)}"
        }
      },
      {
        "type": "origins-powerful-powers:execute",
        "code": "coins -= Math.min(coins, 10);"
      },
      {
        "type": "origins-powerful-powers:with_variables",
        "action": {
          "type": "origins:execute_command",
          "command": "title @s actionbar {\"text\":\"You: ${coins}   Bank: ${Scoreboard.get(\"#bank\", \"team_bank\")}\"}"
        }
      }
    ]
  }
}

A function can show everyone their wallet and the bank:

mcfunction
opp with @a[scores={team_bank=0..}] title @s actionbar {"text":"Bank: ${Scoreboard.has("#bank", "team_bank") ? Scoreboard.get("#bank", "team_bank") : 0}"}
opp with @a title @s actionbar {"text":"Wallet: ${coins}"}

The second /opp with fails as a whole if any player in @a lacks the coins variable. Narrow the selector, for example with a tag, to players who have the power.


Loud footsteps

Your running makes a sculk click that everyone nearby hears, at most every 2 seconds. An ability silences you for 10 seconds.

footsteps.json:

json
{
  "type": "origins:action_over_time",
  "interval": 1,
  "condition": {
    "type": "origins-powerful-powers:vibration_on_cooldown"
  },
  "rising_action": {
    "type": "origins:execute_command",
    "command": "playsound minecraft:block.sculk_sensor.clicking player @a ~ ~ ~ 1 1"
  }
}

hush.json:

json
{
  "type": "origins:active_self",
  "key": {
    "key": "key.origins.secondary_active"
  },
  "cooldown": 600,
  "hud_render": {
    "should_render": true
  },
  "entity_action": {
    "type": "origins-powerful-powers:set_vibration_cooldown",
    "ticks": 200
  }
}

OPP starts a 40-tick cooldown each time you move loudly while it's at 0, so rising_action fires once per "vibration". Hush holds the cooldown up for 200 ticks, which blocks new vibrations (page 6).


Radio

Hold a goat horn to talk to everyone else who has the radio power, anywhere on the server. Needs Simple Voice Chat.

radio.json:

json
{
  "type": "origins:action_over_time",
  "interval": 5,
  "condition": {
    "type": "origins:equipped_item",
    "equipment_slot": "mainhand",
    "item_condition": {
      "type": "origins:ingredient",
      "ingredient": {
        "item": "minecraft:goat_horn"
      }
    }
  },
  "rising_action": {
    "type": "origins-powerful-powers:start_broadcast_voice",
    "listeners": {
      "type": "origins:and",
      "conditions": [
        {
          "type": "origins:target_condition",
          "condition": {
            "type": "origins:power",
            "power": "mypack:radio"
          }
        },
        {
          "type": "origins-powerful-powers:in_voice_range_of",
          "inverted": true
        }
      ]
    }
  },
  "falling_action": {
    "type": "origins-powerful-powers:stop_broadcast_voice"
  }
}

radio_light.json: a spark above you while you're on the air.

json
{
  "type": "origins:action_over_time",
  "interval": 10,
  "condition": {
    "type": "origins-powerful-powers:is_broadcasting_voice"
  },
  "entity_action": {
    "type": "origins:execute_command",
    "command": "particle minecraft:electric_spark ~ ~2.2 ~ 0.2 0.2 0.2 0 4"
  }
}

The broadcast starts when you take the horn in hand and stops when you put it away. Players within normal voice range already hear you, so they're left out (page 13).


Linked inventory

Everyone with the power has their own 9-slot satchel, and the satchels are kept the same. Put something in slot 4 and it appears in everyone else's slot 4. Take it out and it disappears from theirs.

Each player watches their own slots, and when one changes, that player pushes the real stack to everyone else.

linked_inv.json: the satchel itself.

json
{
  "type": "origins:inventory",
  "container_type": "dropper",
  "title": "Linked satchel",
  "key": {
    "key": "key.origins.primary_active"
  }
}

snapshot.json: what each slot looked like last tick, one element per slot.

json
{
  "type": "origins-powerful-powers:list",
  "hidden": true,
  "variable": "snapshot",
  "element_type": "String",
  "max_size": 9,
  "initial": [
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0",
    "minecraft:air x0"
  ]
}

sync.json: every tick, push changed slots, then write down what each slot holds now.

json
{
  "type": "origins:action_over_time",
  "interval": 1,
  "entity_action": {
    "type": "origins:and",
    "actions": [
      {
        "type": "origins:execute_command",
        "command": "tag @s add linked"
      },
      {
        "type": "origins-powerful-powers:for_each_read",
        "list": "snapshot",
        "action": {
          "type": "origins:if_else",
          "condition": {
            "type": "origins-powerful-powers:expression",
            "expression": "!value.equals(Item.id(\"mypack:linked_inv\", index) + \" x\" + Item.count(\"mypack:linked_inv\", index))"
          },
          "if_action": {
            "type": "origins:and",
            "actions": [
              {
                "type": "origins:execute_command",
                "command": "tag @s add linked_source"
              },
              {
                "type": "origins-powerful-powers:with_variables",
                "action": {
                  "type": "origins:execute_command",
                  "command": "execute as @a[tag=linked,tag=!linked_source] run opp powerinv copy power @a[tag=linked_source,limit=1] mypack:linked_inv container.${index} power @s mypack:linked_inv container.${index}"
                }
              },
              {
                "type": "origins:execute_command",
                "command": "tag @s remove linked_source"
              }
            ]
          }
        }
      },
      {
        "type": "origins-powerful-powers:for_each",
        "list": "snapshot",
        "code": "value = Item.id(\"mypack:linked_inv\", index) + \" x\" + Item.count(\"mypack:linked_inv\", index);"
      }
    ]
  }
}

How it works:

  • The fingerprint is Item.id(...) + " x" + Item.count(...), such as minecraft:spruce_log x5, and minecraft:air x0 for an empty slot. It includes the id, so swapping 5 logs for 5 stone counts as a change.
  • for_each_read walks the snapshot and compares each slot with the fingerprint, using index as the slot number.
  • ${index} puts that slot number into the copy command, which copies the real stack.
  • The linked_source tag lets the copy command find the player whose slot changed while running as each other player.
  • for_each then writes the current fingerprints back into the snapshot, so each change pushes once.

Caveats:

  • copy overwrites. If two players change the same slot in the same tick, one push wins and the other item is lost. For a safe version, only push into slots the other player has empty, checked with Item.id.
  • A player who joins keeps their own satchel until someone changes a slot. From then on they follow along.
  • It runs 9 checks per player per tick. For a much bigger satchel, raise interval.

Bond

Two players bond by sneaking and right-clicking each other. Bonded players take 20% less damage from each other's hits. A bond ends by itself once both players have been offline longer than the oppPlayerExpiryDays gamerule.

Code can't read a player's name or UUID, so each player gets a number from a global counter, and a bond's key is made from the two numbers.

bond_id.json: this player's number, handed out once.

json
{
  "type": "origins-powerful-powers:variables",
  "hidden": true,
  "fields": "int bond_id = 0;"
}

assign_id.json: the first tick a player has the power, take the next number.

json
{
  "type": "origins:action_over_time",
  "interval": 20,
  "condition": {
    "type": "origins-powerful-powers:expression",
    "expression": "bond_id == 0"
  },
  "entity_action": {
    "type": "origins-powerful-powers:execute",
    "code": [
      "Global.set(\"mypack:bond_counter\", Global.has(\"mypack:bond_counter\") ? Global.get(\"mypack:bond_counter\") + 1 : 1);",
      "bond_id = Global.get(\"mypack:bond_counter\");"
    ]
  }
}

bond.json: sneak and right-click another player to bond.

json
{
  "type": "origins:action_on_entity_use",
  "condition": {
    "type": "origins:sneaking"
  },
  "bientity_condition": {
    "type": "origins-powerful-powers:expression",
    "expression": "self.bond_id > 0 && other.bond_id > 0"
  },
  "bientity_action": {
    "type": "origins-powerful-powers:execute",
    "code": [
      "Global.set(\"mypack:bond/\" + Math.min(self.bond_id, other.bond_id) + \"/\" + Math.max(self.bond_id, other.bond_id), true);",
      "Global.bind(\"mypack:bond/\" + Math.min(self.bond_id, other.bond_id) + \"/\" + Math.max(self.bond_id, other.bond_id), self);",
      "Global.bind(\"mypack:bond/\" + Math.min(self.bond_id, other.bond_id) + \"/\" + Math.max(self.bond_id, other.bond_id), other);"
    ]
  },
  "cooldown": 20
}

bonded_mercy.json: hits from a bonded player hurt less.

json
{
  "type": "origins:modify_damage_taken",
  "bientity_condition": {
    "type": "origins-powerful-powers:expression",
    "expression": "Global.has(\"mypack:bond/\" + Math.min(self.bond_id, other.bond_id) + \"/\" + Math.max(self.bond_id, other.bond_id))"
  },
  "modifier": {
    "operation": "multiply_total_multiplicative",
    "value": -0.2
  }
}

How it works:

  • The counter is one global integer every player's power reads and writes, so numbers never repeat.
  • The key puts the smaller number first, so A-with-B and B-with-A are the same bond.
  • Binding both players means the key is deleted only after both are gone.
  • The other player needs the powers too. The expression fails (counts as false) for anyone without bond_id.json.
  • In modify_damage_taken, the actor is the attacker and the target is you. The key uses min and max, so the order doesn't matter.