1
0
mirror of https://gitlab.com/rubenwardy/awards.git synced 2025-06-29 14:40:43 +02:00

3 Commits

5 changed files with 236 additions and 277 deletions

369
README.md
View File

@ -7,173 +7,44 @@ With thanks to Wuzzy, kaeza, and MrIbby.
Majority of awards are back ported from Calinou's old fork in Carbone, under same license. Majority of awards are back ported from Calinou's old fork in Carbone, under same license.
# API
## Registering Awards
# Introduction
## Awards and Triggers
An award is a single unlockable unit, registered like so:
```lua ```lua
awards.register_award("mymod:award", { awards.register_award("mymod:myaward", {
description = "My Example Award", description = "The title of the award",
})
```
Awards are unlocked either using `awards.unlock()` or by a trigger being -- Optional:
fullfilled. A trigger is a condition which unlocks an award. Triggers are
registered at the same time as an award is registered:
```lua difficulty = 1.0, -- Difficulty multipler
awards.register_award("mymod:award", {
description = "My Example Award", requires = { "amod:an_award" }, -- don't show this award or allow it to be unlocked
trigger = { -- until required awards are unlocked
type = "dig",
node = "default:stone", sound = {}, -- SimpleSoundSpec or false to play no sound
target = 10, -- if not provided, uses default sound
image = "icon_image.png", -- uses default icon otherwise
background = "background_image.png", -- uses default background otherwise
trigger = { -- is only unlocked by direct calls to awards.unlock() otherwise
type = "trigger_type",
-- see specific docs on the trigger to see what else goes here
}, },
-- Callback. award_def is this table (plus some additional methods/members added by register_award)
on_unlock = function(name, award_def) end,
}) })
``` ```
The above trigger type is an example of a counted_key trigger: If the award is counted, ie: there's a trigger.target property, then the difficulty
rather than a single counter there's a counter per key - in this multipler is timesd by target to get the overal difficulty. If the award isn't a
case the key is the value of the `node` field. If you leave out counted type then the difficulty multiplier is used as the overal difficulty.
the key in a `counted_key` trigger, then the total will be used Award difficulty affects how awards are sorted in a list - more difficult awards
instead. For example, here is an award which unlocks after you've are further down the list.
placed 10 nodes of any type:
```lua
awards.register_award("mymod:award", {
description = "Place 10 nodes!",
trigger = {
type = "place",
target = 10,
},
})
```
You can also register an *Unlock Function*, which can return the name of an
award to unlock it:
```lua
awards.register_award("mymod:award", {
title = "Lava Miner",
description = "Mine any block while being very close to lava.",
})
awards.register_on_dig(function(player, data)
local pos = player:get_pos()
if pos and (minetest.find_node_near(pos, 1, "default:lava_source") or
minetest.find_node_near(pos, 1, "default:lava_flowing")) then
return "mymod:award"
end
return nil
end)
```
The above is a bad example as you don't actually need the stats data given.
It would be better to register a `dignode` callback and call `awards.unlock()`
if the condition is met.
## Trigger Types
The trigger type is used to determine which event will cause the trigger will be
fulfilled. The awards mod comes with a number of predefined types, documented
in [Builtin Trigger Types](#builtin-trigger-types).
Trigger types are registered like so:
```lua
awards.register_trigger("chat", {
type = "counted",
progress = "@1/@2 chat messages",
auto_description = { "Send a chat message", "Chat @1 times" },
})
minetest.register_on_chat_message(function(name, message)
local player = minetest.get_player_by_name(name)
if not player or string.find(message, "/") then
return
end
awards.notify_chat(player)
end)
```
A trigger type has a type as well, which determines how the data is stored and
also how the trigger is fulfilled.
**Trigger Type Types:**
* **custom** requires you handle the calling of awards.unlock() yourself. You also
need to implement on_register() yourself. You'll also probably want to implement
`on_register()` to catch awards registered with your trigger type.
* **counted** stores a single counter for each player which is incremented by calling
`trigger:notify(player)`. Good for homogenous actions like number of chat messages,
joins, and the like.
* **counted_key** stores a table of counters each indexed by a key. There is also
a total field (`__total`) which stores the sum of all counters. A counter is
incremented by calling `trigger:notify(player, key)`. This is good for things like
placing nodes or crafting items, where the key will be the item or node name.
If `key` is an item, then you should also add `key_is_item = true` to the
trigger type definition.
As said, you could use a custom trigger if none of the other ones match your needs.
Here's an example.
```lua
awards.register_trigger("foo", {
type = "counted",
progress = "@1/@2 foos",
auto_description = { "Do a foo", "Foo @1 times" },
})
minetest.register_on_foo(function()
for _, trigger in pairs(awards.on.foo) do
-- trigger is either a trigger tables or
-- or an unlock function.
-- some complex logic
if condition then
awards.unlock(trigger)
end
end
end)
```
## Award Difficulty
Difficulty is used to determine how awards are sorted in awards lists.
If the award trigger is counted, ie: the trigger requires a `target` property,
then the difficulty multipler is timesd by `target` to get the overall difficulty.
If the award isn't a counted type then the difficulty multiplier is used as the
overal difficulty. Award difficulty affects how awards are sorted in a list -
more difficult awards are further down the list.
In real terms, `difficulty` is a relative difficulty to do one unit of the trigger
if its counted, otherwise it's the relative difficulty of completely doing the
award (if not-counted). For the `dig` trigger type, 1 unit would be 1 node dug.
Actual code used to calculate award difficulty: Actual code used to calculate award difficulty:
@ -184,46 +55,71 @@ if def.trigger and def.trigger.target then
end end
``` ```
## Registering Trigger Types
# API ```lua
local trigger = awards.register_trigger(name, {
type = "", -- type of trigger, defaults to custom
progress = "%2/%2"
auto_description = { "Mine: @2", "Mine: @1×@2" },
on_register = function(self, def) end,
-- "counted_key" only, when no key is given (ie: a total)
auto_description_total = { "Mine @1 block.", "Mine @1 blocks." },
-- "counted_key" only, get key for particular award - return nil for a total
get_key = function(self, def)
return minetest.registered_aliases[def.trigger.node] or def.trigger.node
end,
})
```
Types:
* "custom" requires you handle the calling of awards.unlock() yourself. You also
need to implement on_register() yourself.
* "counted" stores a single counter for each player which is incremented by calling
trigger:notify(player). Good for homogenous actions like number of chat messages,
joins, and the like.
* "counted_key" stores a table of counters each indexed by a key. There is also
a total field (`__total`) which stores the sum of all counters. A counter is
incremented by calling trigger:notify(player, key). This is good for things like
placing nodes or crafting items, where the key will be the item or node name.
## Helper Functions
* awards.register_award(name, def), the def table has the following fields:
* `description` - the title of the award. Required.
* `difficulty` - see [Award Difficulty](#award-difficulty).
* `requires` - list of awards that need to be unlocked before this one
is visible.
* `sound` - `SimpleSoundSpec` table to play on unlock.
`false` to disable unlock sound.
* `image` - the icon image, use default otherwise.
* `background` - the background image, use default otherwise.
* `trigger` - trigger definition, see [Builtin Trigger Types](#builtin-trigger-types).
* `on_unlock(name, def)` - callback on unlock.
* awards.register_trigger(name, def), the def table has the following fields:
* `type` - see [Trigger Types](#trigger-types).
* `progress` - used to format progress, defaults to "%1/%2".
* `auto_description` - a table of two elements. Each element is a format string. Element 1 is singular, element 2 is plural. Used for the award description (not title) if none is given.
* `on_register(award_def)` - called when an award registers with this type.
* "counted_key" only:
* `auto_description_total` - Used if the trigger is for the total.
* `get_key(self, def)` - get key for particular award, return nil for a total.
* `key_is_item` - true if the key is an item name. On notify(),
any watched groups will also be notified as `group:groupname` keys.
* awards.register_on_unlock(func(name, def)) * awards.register_on_unlock(func(name, def))
* name is the player name * name is the player name
* def is the award def. * def is the award def.
* return true to cancel HUD * return true to cancel HUD
* awards.unlock(name, award) * awards.unlock(name, award)
* gives an award to a player * gives an award to a player
* name is the player name * name is the player name
## Builtin Trigger Types # Included in the Mod
The API, above, allows you to register awards
and triggers (things that look for events and unlock awards, they need
to be registered in order to get details from award_def.trigger).
However, all awards and triggers are separate from the API.
They can be found in init.lua and triggers.lua
## Triggers
Callbacks (register a function to be run) Callbacks (register a function to be run)
"dig", "place", "craft", "death", "chat", "join" or "eat"
* dig type: Dig a node. * dig type: Dig a node.
* node: the dug node type. If nil, all dug nodes are counted * node: the dug node type. If nil, all dug nodes are counted
* place type: Place a node. * place type: Place a node.
* node: the placed node type. If nil, all placed nodes are counted * node: the placed node type. If nil, all placed nodes are counted
* eat type: Eat an item.
* item: the eaten item type. If nil, all eaten items are counted
* craft type: Craft something. * craft type: Craft something.
* item: the crafted item type. If nil, all crafted items are counted * item: the crafted item type. If nil, all crafted items are counted
* death type: Die. * death type: Die.
@ -231,81 +127,72 @@ Callbacks (register a function to be run)
or nil for total deaths. or nil for total deaths.
* chat type: Write a chat message. * chat type: Write a chat message.
* join type: Join the server. * join type: Join the server.
* eat type: Eat an item. * (for all types) target - how many times to dig/place/craft/etc.
* item: the eaten item type. If nil, all eaten items are counted * See Triggers
(for all types) target - how many times to dig/place/craft/etc.
Each type has a register function like so:
* awards.register_on_TRIGGERTYPE(func(player, data))
* data is the player stats data
* return award name or null
### dig ### dig
```lua trigger = {
trigger = { type = "dig",
type = "dig", node = "default:dirt",
node = "default:dirt", -- item, alias, or group target = 50,
target = 50, }
}
```
### place ### place
```lua trigger = {
trigger = { type = "place",
type = "place", node = "default:dirt",
node = "default:dirt", -- item, alias, or group target = 50,
target = 50, }
}
```
### craft
```lua
trigger = {
type = "craft",
node = "default:dirt", -- item, alias, or group
target = 50,
}
```
### death ### death
```lua trigger = {
trigger = { type = "death",
type = "death", reason = "fall",
reason = "fall", target = 5,
target = 5, }
}
```
### chat ### chat
```lua trigger = {
trigger = { type = "chat",
type = "chat", target = 100,
target = 100, }
}
```
### join ### join
```lua trigger = {
trigger = { type = "join",
type = "join", target = 100,
target = 100, }
}
```
### eat ### eat
```lua trigger = {
trigger = { type = "eat",
type = "eat", item = "default:apple",
item = "default:apple", target = 100,
target = 100, }
}
``` ## Callbacks relating to triggers
* awards.register_on_dig(func(player, data))
* data is player data (see below)
* return award name or null
* awards.register_on_place(func(player, data))
* data is player data (see below)
* return award name or null
* awards.register_on_eat(func(player, data))
* data is player data (see below)
* return award name or null
* awards.register_on_death(func(player, data))
* data is player data (see below)
* return award name or null
* awards.register_on_chat(func(player, data))
* data is player data (see below)
* return award name or null
* awards.register_on_join(func(player, data)
* data is player data (see below)
* return award name or null

29
api.lua
View File

@ -178,14 +178,8 @@ function awards.register_trigger(tname, tdef)
awards["notify_" .. tname] = tdef.notify awards["notify_" .. tname] = tdef.notify
elseif tdef.type == "counted_key" then elseif tdef.type == "counted_key" then
if tdef.key_is_item then
tdef.watched_groups = {}
end
-- On award register
local old_reg = tdef.on_register local old_reg = tdef.on_register
function tdef:on_register(def) function tdef:on_register(def)
-- Register trigger
local tmp = { local tmp = {
award = def.name, award = def.name,
key = tdef:get_key(def), key = tdef:get_key(def),
@ -193,12 +187,6 @@ function awards.register_trigger(tname, tdef)
} }
tdef.register(tmp) tdef.register(tmp)
-- If group, add it to watch list
if tdef.key_is_item and tmp.key and tmp.key:sub(1, 6) == "group:" then
tdef.watched_groups[tmp.key:sub(7, #tmp.key)] = true
end
-- Called to get progress values and labels
function def.getProgress(_, data) function def.getProgress(_, data)
local done local done
data[tname] = data[tname] or {} data[tname] = data[tname] or {}
@ -213,7 +201,6 @@ function awards.register_trigger(tname, tdef)
} }
end end
-- Build description if none is specificed by the award
function def.getDefaultDescription(_) function def.getDefaultDescription(_)
local n = def.trigger.target local n = def.trigger.target
if tmp.key then if tmp.key then
@ -226,7 +213,6 @@ function awards.register_trigger(tname, tdef)
end end
end end
-- Call on_register in trigger type definition
if old_reg then if old_reg then
return old_reg(tdef, def) return old_reg(tdef, def)
end end
@ -235,17 +221,6 @@ function awards.register_trigger(tname, tdef)
function tdef.notify(player, key, n) function tdef.notify(player, key, n)
n = n or 1 n = n or 1
if tdef.key_is_item and key:sub(1, 6) ~= "group:" then
local itemdef = minetest.registered_items[key]
if itemdef then
for groupname, _ in pairs(itemdef.groups or {}) do
if tdef.watched_groups[groupname] then
tdef.notify(player, "group:" .. groupname, n)
end
end
end
end
assert(player and player.is_player and player:is_player() and key) assert(player and player.is_player and player:is_player() and key)
local name = player:get_player_name() local name = player:get_player_name()
local data = awards.player(name) local data = awards.player(name)
@ -254,9 +229,7 @@ function awards.register_trigger(tname, tdef)
data[tname] = data[tname] or {} data[tname] = data[tname] or {}
local currentVal = (data[tname][key] or 0) + n local currentVal = (data[tname][key] or 0) + n
data[tname][key] = currentVal data[tname][key] = currentVal
if key:sub(1, 6) ~= "group:" then data[tname].__total = (data[tname].__total or 0) + n
data[tname].__total = (data[tname].__total or 0) + n
end
tdef:run_callbacks(player, data, function(entry) tdef:run_callbacks(player, data, function(entry)
local current local current

View File

@ -958,3 +958,45 @@ if minetest.get_modpath("nyancat") then
} }
}) })
end end
if minetest.get_modpath("mobs_animal") then
awards.register_award("mobs:kill_chicken", {
title = S("Chicken"),
description = S("A test award when mobs mod on"),
difficulty = 0.001,
trigger = {
type = "kill_mob",
mob = "mobs_animal:chicken",
target = 5
}
})
awards.register_award("mobs:kill_cow", {
title = S("Cows"),
description = S("A test award when mobs mod on"),
difficulty = 0.001,
trigger = {
type = "kill_mob",
mob = "mobs_animal:cow",
target = 5
}
})
awards.register_award("mobs:kill_all", {
title = S("All"),
description = S("A test award when mobs mod on"),
difficulty = 0.001,
trigger = {
type = "kill_mob",
target = 5
}
})
end
awards.register_award("pnch_entity_test", {
title = S("All"),
description = S("A test award for punching"),
difficulty = 0.001,
trigger = {
type = "punch_entity",
target = 5
}
})

View File

@ -12,3 +12,4 @@ moreblocks?
fire? fire?
flowers? flowers?
nyancat? nyancat?
mobs_animal?

View File

@ -64,8 +64,7 @@ awards.register_trigger("dig", {
auto_description_total = { "Mine @1 block.", "Mine @1 blocks." }, auto_description_total = { "Mine @1 block.", "Mine @1 blocks." },
get_key = function(self, def) get_key = function(self, def)
return minetest.registered_aliases[def.trigger.node] or def.trigger.node return minetest.registered_aliases[def.trigger.node] or def.trigger.node
end, end
key_is_item = true,
}) })
minetest.register_on_dignode(function(pos, node, player) minetest.register_on_dignode(function(pos, node, player)
if not player or not pos or not node then if not player or not pos or not node then
@ -85,8 +84,7 @@ awards.register_trigger("place", {
auto_description_total = { "Place @1 block.", "Place @1 blocks." }, auto_description_total = { "Place @1 block.", "Place @1 blocks." },
get_key = function(self, def) get_key = function(self, def)
return minetest.registered_aliases[def.trigger.node] or def.trigger.node return minetest.registered_aliases[def.trigger.node] or def.trigger.node
end, end
key_is_item = true,
}) })
minetest.register_on_placenode(function(pos, node, player) minetest.register_on_placenode(function(pos, node, player)
if not player or not pos or not node then if not player or not pos or not node then
@ -106,8 +104,7 @@ awards.register_trigger("craft", {
auto_description_total = { "Craft @1 item", "Craft @1 items." }, auto_description_total = { "Craft @1 item", "Craft @1 items." },
get_key = function(self, def) get_key = function(self, def)
return minetest.registered_aliases[def.trigger.item] or def.trigger.item return minetest.registered_aliases[def.trigger.item] or def.trigger.item
end, end
key_is_item = true,
}) })
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
if not player or itemstack:is_empty() then if not player or itemstack:is_empty() then
@ -127,8 +124,7 @@ awards.register_trigger("eat", {
auto_description_total = { "Eat @1 item", "Eat @1 items." }, auto_description_total = { "Eat @1 item", "Eat @1 items." },
get_key = function(self, def) get_key = function(self, def)
return minetest.registered_aliases[def.trigger.item] or def.trigger.item return minetest.registered_aliases[def.trigger.item] or def.trigger.item
end, end
key_is_item = true,
}) })
minetest.register_on_item_eat(function(_, _, itemstack, player, _) minetest.register_on_item_eat(function(_, _, itemstack, player, _)
if not player or itemstack:is_empty() then if not player or itemstack:is_empty() then
@ -139,3 +135,63 @@ minetest.register_on_item_eat(function(_, _, itemstack, player, _)
itemname = minetest.registered_aliases[itemname] or itemname itemname = minetest.registered_aliases[itemname] or itemname
awards.notify_craft(player, itemname, itemstack:get_count()) awards.notify_craft(player, itemname, itemstack:get_count())
end) end)
-- trigger for killing entities with more than 1 point of health
awards.register_trigger("kill_mob", {
type = "counted_key",
progress = "@1/@2 killed",
auto_description = { "Kill @2", "Kill @1×@2" },
auto_description_total = { "Kill @1 mob", "Kill @1 mobs." },
get_key = function(self, def)
return minetest.registered_aliases[def.trigger.mob] or def.trigger.mob
end
})
-- wait for all mobs to be registered
minetest.after(0, function()
local ents = minetest.registered_entities
local mobs = {}
for _, ent in pairs(ents) do
if ent.hp_max ~= nil and ent.hp_max > 0 then
table.insert(mobs,ent)
end
end
for _, mob in pairs(mobs) do
local old_func = mob.on_punch
mob.on_punch = function(self, player, a, b, c, d)
old_func(self, player, a, b, c, d)
if player and player:is_player() then
if self.health <= 0 then
awards.notify_kill_mob(player, self.name)
end
end
end
end
end)
-- trigger for killing an entity
awards.register_trigger("punch_entity", {
type = "counted_key",
progress = "@1/@2 punched",
auto_description = { "Punch @2", "Punch @1×@2" },
auto_description_total = { "Punch @1 entity", "Punch @1 entities." },
get_key = function(self, def)
return minetest.registered_aliases[def.trigger.mob] or def.trigger.mob
end
})
-- wait for all entities to be registered
minetest.after(0, function()
local ents = minetest.registered_entities
for _, ent in pairs(ents) do
if ent.hp_max == nil then
local old_func = ent.on_punch
ent.on_punch = function(self, player, a, b, c, d)
old_func(self, player, a, b, c, d)
if player and player:is_player() then
awards.notify_punch_entity(player, self.name)
end
end
end
end
end)