Initial commit
59
API.txt
|
@ -1,59 +0,0 @@
|
|||
|
||||
Survival Mod API
|
||||
----------------
|
||||
This is the Application Program Interface for the Survival Lib.
|
||||
Please note that this API is still WIP, so it may change in the future.
|
||||
|
||||
Core
|
||||
----
|
||||
|
||||
survival.create_meter(name, meterdef)
|
||||
Creates a new custom meter. <name> must follow the `modname:itemname'
|
||||
naming convention.
|
||||
|
||||
meterdef = {
|
||||
description = "My Fabulous Meter"
|
||||
Description for the inventory.
|
||||
command = <metercmddef>
|
||||
Used to register a chat command to see the current value. See below.
|
||||
recipe = { <recipe table> }
|
||||
Recipe used to craft the meter item.
|
||||
image = "modname_texname.png"
|
||||
Image used both for wielding and in inventory.
|
||||
get_value = func(player)
|
||||
Must return the value to show both in the status message and in the
|
||||
meter item. Returned value must be in range [0..100].
|
||||
on_use = func(itemstack, user, pointed_thing)
|
||||
Callback for when the item is used. Same as for itemdef.
|
||||
}
|
||||
|
||||
metercmddef = {
|
||||
name = "foo"
|
||||
Name for the chat command.
|
||||
label = "Foo"
|
||||
Label for the chat command. If not specified defaults to <name>.
|
||||
Invoking /cmdname outputs the following:
|
||||
Label: [67%] ||||||
|
||||
}
|
||||
|
||||
survival.distance3d(p1, p2)
|
||||
Returns the distance between two 3D points. <p1> and <p2> are position
|
||||
tables ({x=X,y=Y,z=Z}).
|
||||
|
||||
Drowning Mod
|
||||
------------
|
||||
|
||||
survival.drowning.register_liquid(name)
|
||||
Registers the specified node name as liquid. Please rememeber you need
|
||||
to register BOTH the _source and _flowing variants! Liquids registered
|
||||
by this function are checked to see if the player is drowning, and are
|
||||
also checked by the air bubbles. The mod already registers water, lava,
|
||||
and oil (from the Oil Mod), both in source and flowing form.
|
||||
|
||||
Hunger Mod
|
||||
----------
|
||||
|
||||
survival.hunger.register_food(name)
|
||||
Registers a new item as food. Please note that the mod registers most
|
||||
known food items automatically, and also registers any item with the
|
||||
"food" and/or "eatable" groups.
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
Version 0.1.2:
|
||||
- common: Lava Suit mod is now into Survival Modpack, in the form of
|
||||
Hazard Suit.
|
||||
- hazards: Now we override the default lava nodes to damage players when
|
||||
they don't have the suit, instead of healing when player has the suit.
|
||||
Thanks to PilzAdam for the suggestion.
|
||||
- hazards: There is now a `register_material' function to define new
|
||||
hazardous materials from other mods.
|
||||
|
||||
Version 0.1.1:
|
||||
- hunger: Changed way in which eating is detected.
|
||||
- hunger: Fixed bug where default:apple was not taken as food.
|
||||
- lib: Code refactoring and new API.
|
||||
- common: Added thirst.
|
||||
- common: Added meters/gauges.
|
||||
- common: States are now preserved across sessions.
|
||||
|
||||
Version 0.1.0:
|
||||
- Added hunger and drowning.
|
|
@ -23,7 +23,7 @@ INSTALLING
|
|||
Unpack the modpack into one of the directories where Minetest looks for mods.
|
||||
For more information, see http://wiki.minetest.com/wiki/Installing_mods
|
||||
|
||||
See the respective mods' README.txt for information about technical aspects
|
||||
See the respective mods' TECHNOTE.txt for information about technical aspects
|
||||
of the implementation and known bugs.
|
||||
|
||||
CONFIGURING
|
||||
|
|
|
@ -2,17 +2,24 @@
|
|||
Drowning Mod for Minetest
|
||||
This mod is part of the Survival Modpack for Minetest.
|
||||
Copyright (C) 2013 Diego Martínez <lkaezadl3@gmail.com>
|
||||
Inspired by the existing drowning mod [TODO: who's the author?]
|
||||
Inspired by the existing drowning mod by randomproof.
|
||||
|
||||
See the file `../LICENSE.txt' for information about distribution.
|
||||
|
||||
TECHNICAL NOTES
|
||||
---------------
|
||||
To detect if the player is under water (to restore the oxygen timer), this
|
||||
mod has to know about all the nodes considered "liquid". It currently handles
|
||||
water_{source|flowing}, lava_{source|flowing}, and oil_{source|flowing} from
|
||||
the Oil Mod.
|
||||
This mod adds drowning to the game. The player can drown after being under
|
||||
liquid (water, lava, etc.) for some time without breathing air again.
|
||||
|
||||
The original drowning mod checked whether or not there was an air node at the
|
||||
player's head, but this was inaccurate as you could "drown" by standing
|
||||
"inside" a torch node, or other walkable nodes.
|
||||
When drowning, the player will be hurt (and will hear a hurt sound); If the
|
||||
player does not get oxygen, he/she will be damaged again, and so on until
|
||||
he does or dies.
|
||||
|
||||
You can also craft a meter (or gauge). It shows your current hunger by means
|
||||
of a colored bar under the item (like tool wear). Craft it like this:
|
||||
|
||||
+-------------+-------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+-------------+-------------+
|
||||
| wood planks | glass | wood planks |
|
||||
+-------------+-------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+-------------+-------------+
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
TECHNICAL NOTES ABOUT DROWNING
|
||||
------------------------------
|
||||
To detect if the player is under water (to restore the oxygen timer), this
|
||||
mod has to know about all the nodes considered "liquid". It currently handles
|
||||
water_{source|flowing}, lava_{source|flowing}, and oil_{source|flowing} from
|
||||
the Oil Mod.
|
||||
|
||||
The original drowning mod checked whether or not there was an air node at the
|
||||
player's head, but this was inaccurate as you could "drown" by standing
|
||||
"inside" a torch node, or other walkable nodes.
|
||||
|
||||
Unfortunately, both ways have disadvantages; as was said, in the old
|
||||
implementation, you can drown by being inside torches, lamps, etc. In the
|
||||
new implementation, however, you can catch breath by placing any node not
|
||||
considered "liquid" (for example, cobblestone) on your head.
|
|
@ -1,17 +1,9 @@
|
|||
|
||||
drowning = { };
|
||||
|
||||
local player_state = { };
|
||||
|
||||
local START_DROWNING_TIME = survival.conf_getnum("drowning.damage_start_time", 20);
|
||||
local DROWNING_TIME = survival.conf_getnum("drowning.damage_interval", 2);
|
||||
local DROWNING_DAMAGE = survival.conf_getnum("drowning.damage", 1);
|
||||
local DTIME = survival.conf_getnum("drowning.check_interval", 0.5);
|
||||
|
||||
local timer = 0;
|
||||
|
||||
local liquids = { };
|
||||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
if (minetest.get_modpath("intllib")) then
|
||||
|
@ -21,6 +13,10 @@ else
|
|||
S = function ( s ) return s; end
|
||||
end
|
||||
|
||||
local liquids = { };
|
||||
|
||||
local player_state = { };
|
||||
|
||||
minetest.register_entity("survival_drowning:bubbles", {
|
||||
physical = false;
|
||||
timer = 0;
|
||||
|
@ -39,64 +35,6 @@ minetest.register_entity("survival_drowning:bubbles", {
|
|||
end;
|
||||
});
|
||||
|
||||
if (minetest.setting_getbool("enable_damage") and survival.conf_getbool("drowning.enabled", true)) then
|
||||
print("survival_drowning: Drowning is enabled!");
|
||||
minetest.register_globalstep(function ( dtime )
|
||||
timer = timer + dtime;
|
||||
if (timer < DTIME) then
|
||||
return;
|
||||
end
|
||||
timer = timer - DTIME;
|
||||
for k, v in pairs(minetest.get_connected_players()) do
|
||||
name = v:get_player_name();
|
||||
if (not player_state[name]) then
|
||||
player_state[name] = { count=0, drowning=false };
|
||||
end
|
||||
local puw = player_state[name];
|
||||
local pos = v:getpos()
|
||||
pos.y = pos.y + 1;
|
||||
if (is_player_under_liquid(v)) then
|
||||
state.count = state.count + 0.5;
|
||||
if (math.random(1, 100) < 20) then
|
||||
if (not liquids[minetest.env:get_node({ x=pos.x; y=pos.y+8; z=pos.z}).name]) then
|
||||
local bub = minetest.env:add_entity(pos, "survival_drowning:bubbles");
|
||||
bub:setvelocity({ x=0; y=1; z=0 });
|
||||
end
|
||||
end
|
||||
if ((not state.drowning) and (state.count >= START_DROWNING_TIME)) then
|
||||
v:set_hp(v:get_hp() - DROWNING_DAMAGE);
|
||||
minetest.sound_play({ name="drowning_gurp"; }, { pos = pos; gain = 1.0; max_hear_distance = 16; });
|
||||
state.drowning = true;
|
||||
state.count = state.count - START_DROWNING_TIME;
|
||||
minetest.chat_send_player(name, S("You are out of oxygen."));
|
||||
elseif (state.drowning and (state.count >= DROWNING_TIME)) then
|
||||
v:set_hp(v:get_hp() - DROWNING_DAMAGE);
|
||||
minetest.sound_play({ name="drowning_gurp"; }, { pos = pos; gain = 1.0; max_hear_distance = 16; });
|
||||
state.count = state.count - DROWNING_TIME;
|
||||
if (v:get_hp() <= 0) then
|
||||
minetest.chat_send_player(name, S("You drowned."));
|
||||
end
|
||||
end
|
||||
else
|
||||
if (state.count > 0) then
|
||||
pos = v:getpos();
|
||||
pos.y = pos.y + 1;
|
||||
minetest.sound_play({ name="drowning_gasp" }, { pos = pos; gain = 1.0; max_hear_distance = 32; });
|
||||
end
|
||||
state.count = 0;
|
||||
state.drowning = false;
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function is_player_under_liquid(player)
|
||||
local pos = player:getpos()
|
||||
pos.y = pos.y + 1.5;
|
||||
|
||||
return (liquids[minetest.env:get_node(pos).name]);
|
||||
end
|
||||
|
||||
survival.drowning = { };
|
||||
|
||||
survival.drowning.register_liquid = function ( name )
|
||||
|
@ -112,39 +50,78 @@ survival.drowning.is_liquid_at_pos = function ( pos )
|
|||
return liquids[name];
|
||||
end
|
||||
|
||||
survival.drowning.is_player_under_liquid = function ( player )
|
||||
local pos = player:getpos()
|
||||
pos.y = pos.y + 1.5;
|
||||
return (liquids[minetest.env:get_node(pos).name]);
|
||||
end
|
||||
|
||||
survival.drowning.register_liquid("default:water_source");
|
||||
survival.drowning.register_liquid("default:water_flowing");
|
||||
survival.drowning.register_liquid("default:lava_source");
|
||||
survival.drowning.register_liquid("default:lava_flowing");
|
||||
survival.drowning.register_liquid("oil:oil_source");
|
||||
survival.drowning.register_liquid("oil:oil_flowing");
|
||||
survival.drowning.register_liquid("survival_hazards:toxic_waste_source");
|
||||
survival.drowning.register_liquid("survival_hazards:toxic_waste_flowing");
|
||||
|
||||
survival.create_meter("survival_drowning:meter", {
|
||||
description = S("Oxygen Meter");
|
||||
command = {
|
||||
name = "o2";
|
||||
label = S("Oxygen");
|
||||
};
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "default:glass", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
image = "survival_drowning_meter.png";
|
||||
survival.register_state("oxygen", {
|
||||
label = S("Oxygen");
|
||||
not_in_plstats = true;
|
||||
get_value = function ( player )
|
||||
local name = player:get_player_name();
|
||||
if (player_state[name].drowning) then
|
||||
return 0.01;
|
||||
item = {
|
||||
name = "survival_drowning:meter";
|
||||
description = S("Oxygen Meter");
|
||||
inventory_image = "survival_drowning_meter.png";
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "default:glass", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
};
|
||||
get_default = function ( )
|
||||
return {
|
||||
count = 0;
|
||||
flag = false;
|
||||
};
|
||||
end;
|
||||
get_scaled_value = function ( state )
|
||||
if (state.flag) then
|
||||
return 0;
|
||||
else
|
||||
return 100 * (START_DROWNING_TIME - player_state[name].count) / START_DROWNING_TIME;
|
||||
return 100 * (START_DROWNING_TIME - state.count) / START_DROWNING_TIME;
|
||||
end
|
||||
end;
|
||||
on_update = function ( dtime, player, state )
|
||||
local pos = player:getpos();
|
||||
pos.y = pos.y + 1;
|
||||
if (survival.drowning.is_player_under_liquid(player)) then
|
||||
state.count = state.count + dtime;
|
||||
if (math.random(1, 100) < 20) then
|
||||
if (not liquids[minetest.env:get_node({ x=pos.x; y=pos.y+8; z=pos.z}).name]) then
|
||||
local bub = minetest.env:add_entity(pos, "survival_drowning:bubbles");
|
||||
bub:setvelocity({ x=0; y=1; z=0 });
|
||||
end
|
||||
end
|
||||
if ((not state.flag) and (state.count >= START_DROWNING_TIME)) then
|
||||
player:set_hp(player:get_hp() - DROWNING_DAMAGE);
|
||||
minetest.sound_play({ name="drowning_gurp"; }, { pos = pos; gain = 1.0; max_hear_distance = 16; });
|
||||
state.flag = true;
|
||||
state.count = 0;
|
||||
minetest.chat_send_player(player:get_player_name(), S("You are out of oxygen."));
|
||||
elseif (state.flag and (state.count >= DROWNING_TIME)) then
|
||||
player:set_hp(player:get_hp() - DROWNING_DAMAGE);
|
||||
minetest.sound_play({ name="drowning_gurp"; }, { pos = pos; gain = 1.0; max_hear_distance = 16; });
|
||||
state.count = 0;
|
||||
if (player:get_hp() <= 0) then
|
||||
minetest.chat_send_player(name, S("You drowned."));
|
||||
end
|
||||
end
|
||||
else
|
||||
if (state.count > 0) then
|
||||
minetest.sound_play({ name="drowning_gasp" }, { pos = pos; gain = 1.0; max_hear_distance = 32; });
|
||||
end
|
||||
state.count = 0;
|
||||
state.flag = false;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
minetest.register_on_joinplayer(function ( player )
|
||||
player_state[player:get_player_name()] = {
|
||||
count = 0;
|
||||
drowning = false;
|
||||
};
|
||||
end);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
Hazard Suit Mod for Minetest
|
||||
This mod is part of the Survival Modpack for Minetest.
|
||||
Copyright (C) 2013 Diego Martínez <lkaezadl3@gmail.com>
|
||||
|
||||
See the file `../LICENSE.txt' for information about distribution.
|
||||
|
||||
This mod adds a "Hazard Suit" to the game. The player is able to swim or
|
||||
otherwise enter "hazardous nodes" such as lava without being damaged, as long
|
||||
as the suit has energy left.
|
||||
|
||||
Currently, only swimming in lava is implemented. The mod exports a basic API
|
||||
to register new hazardous nodes from other mods. This mod also adds a test
|
||||
"Toxic Waste" liquid which is currently only obtainable via creative mode,
|
||||
or by using the /give and /giveme chat commands.
|
||||
|
||||
The hazard suit is based on the Lava Suit mod by yours truly, expanded to
|
||||
be able to cope with other hazardous materials.
|
|
@ -0,0 +1,3 @@
|
|||
default
|
||||
bucket
|
||||
survival_lib
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
local CHECK_INTERVAL = 0.25;
|
||||
local DAMAGE_INTERVAL = 1;
|
||||
local WEAR_PER_HP = 400;
|
||||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
if (minetest.get_modpath("intllib")) then
|
||||
dofile(minetest.get_modpath("intllib").."/intllib.lua");
|
||||
S = intllib.Getter(minetest.get_current_modname());
|
||||
else
|
||||
S = function ( s ) return s; end
|
||||
end
|
||||
|
||||
minetest.register_tool("survival_hazards:suit", {
|
||||
description = S("Hazard Suit");
|
||||
groups = { };
|
||||
inventory_image = "survival_hazards_suit.png";
|
||||
});
|
||||
|
||||
minetest.register_craft({
|
||||
output = 'survival_hazards:suit';
|
||||
recipe = {
|
||||
{ '', 'bucket:bucket_empty', '' },
|
||||
{ 'default:mese_crystal', 'default:mese_crystal', 'default:mese_crystal' },
|
||||
{ '', 'default:mese_crystal', '' },
|
||||
};
|
||||
});
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless";
|
||||
output = 'survival_hazards:suit';
|
||||
recipe = {
|
||||
'survival_hazards:suit',
|
||||
'default:mese_crystal',
|
||||
};
|
||||
});
|
||||
|
||||
local dtime_count = 0;
|
||||
|
||||
local function override ( name )
|
||||
local nodedef = minetest.registered_nodes[name];
|
||||
nodedef.damage_per_second = nil;
|
||||
minetest.register_node(":"..name, nodedef);
|
||||
end
|
||||
|
||||
local materials = { };
|
||||
|
||||
survival.hazards = { };
|
||||
|
||||
survival.hazards.register_material = function ( nodename, matdef )
|
||||
matdef.damage = (
|
||||
matdef.damage
|
||||
or minetest.registered_nodes[nodenames].damage_per_second
|
||||
or 0
|
||||
);
|
||||
materials[nodename] = matdef;
|
||||
override(nodename);
|
||||
end
|
||||
|
||||
survival.hazards.register_liquid = function ( nodename, matdef )
|
||||
survival.hazards.register_material(nodename.."_source", matdef);
|
||||
survival.hazards.register_material(nodename.."_flowing", matdef);
|
||||
end
|
||||
|
||||
survival.hazards.get_material_damage = function ( nodename )
|
||||
return materials[nodename] or 0;
|
||||
end
|
||||
|
||||
survival.hazards.register_liquid("default:lava", {
|
||||
damage = 8;
|
||||
});
|
||||
|
||||
dofile(minetest.get_modpath("survival_hazards").."/toxicwaste.lua");
|
||||
|
||||
local players_in_hazard = { };
|
||||
|
||||
minetest.register_globalstep(function ( dtime )
|
||||
dtime_count = dtime_count + dtime;
|
||||
if (dtime_count >= CHECK_INTERVAL) then
|
||||
local count = dtime_count;
|
||||
dtime_count = dtime_count - CHECK_INTERVAL;
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local pos = player:getpos();
|
||||
local nodey0 = minetest.env:get_node(pos).name;
|
||||
local nodey1 = minetest.env:get_node({ x=pos.x, y=pos.y+1, z=pos.z }).name;
|
||||
local name = player:get_player_name();
|
||||
local dmg0 = (materials[nodey0] and materials[nodey0].damage);
|
||||
local dmg1 = (materials[nodey1] and materials[nodey1].damage);
|
||||
if (dmg0 or dmg1) then
|
||||
players_in_hazard[name] = (players_in_hazard[name] or 0) + count;
|
||||
if (players_in_hazard[name] >= DAMAGE_INTERVAL) then
|
||||
players_in_hazard[name] = 0;
|
||||
local damage;
|
||||
local matdef;
|
||||
dmg0 = dmg0 or 0;
|
||||
dmg1 = dmg1 or 0;
|
||||
if (dmg0 > dmg1) then
|
||||
damage = dmg0;
|
||||
matdef = materials[nodey0];
|
||||
else
|
||||
damage = dmg1;
|
||||
matdef = materials[nodey1];
|
||||
end
|
||||
local wear = damage * WEAR_PER_HP;
|
||||
local inv = player:get_inventory();
|
||||
local stack;
|
||||
local index;
|
||||
for i = 1, inv:get_size("main") do
|
||||
stack = inv:get_stack("main", i);
|
||||
if ((stack:get_name() == "survival_hazards:suit")
|
||||
and (stack:get_wear() > wear)) then
|
||||
index = i;
|
||||
break;
|
||||
end
|
||||
end
|
||||
if (index) then
|
||||
stack:add_wear(wear);
|
||||
inv:set_stack("main", index, stack);
|
||||
-- TODO: Add "use" sound.
|
||||
else
|
||||
player:set_hp(player:get_hp() - damage);
|
||||
-- TODO: Add "damage" sound.
|
||||
end
|
||||
end
|
||||
else
|
||||
players_in_hazard[name] = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
end);
|
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 504 B |
After Width: | Height: | Size: 650 B |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
|
@ -0,0 +1,77 @@
|
|||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
if (minetest.get_modpath("intllib")) then
|
||||
dofile(minetest.get_modpath("intllib").."/intllib.lua");
|
||||
S = intllib.Getter(minetest.get_current_modname());
|
||||
else
|
||||
S = function ( s ) return s; end
|
||||
end
|
||||
|
||||
minetest.register_node("survival_hazards:toxic_waste_flowing", {
|
||||
description = S("Toxic Waste Flowing");
|
||||
inventory_image = minetest.inventorycube("survival_hazards_waste.png");
|
||||
drawtype = "flowingliquid";
|
||||
tiles = { "survival_hazards_waste.png" };
|
||||
special_tiles = {
|
||||
{
|
||||
image="survival_hazards_waste_flw_anim.png",
|
||||
backface_culling=false,
|
||||
animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
|
||||
},
|
||||
{
|
||||
image="survival_hazards_waste_flw_anim.png",
|
||||
backface_culling=true,
|
||||
animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
|
||||
},
|
||||
};
|
||||
paramtype = "light",
|
||||
walkable = false;
|
||||
pointable = false;
|
||||
diggable = false;
|
||||
buildable_to = true;
|
||||
drop = "";
|
||||
liquidtype = "flowing";
|
||||
liquid_alternative_flowing = "survival_hazards:toxic_waste_flowing";
|
||||
liquid_alternative_source = "survival_hazards:toxic_waste_source";
|
||||
liquid_viscosity = 1;
|
||||
damage_per_second = 2;
|
||||
post_effect_color = { a=192, r=128, g=255, b=128};
|
||||
groups = { liquid=2; not_in_creative_inventory=1 };
|
||||
})
|
||||
|
||||
minetest.register_node("survival_hazards:toxic_waste_source", {
|
||||
description = S("Toxic Waste Source");
|
||||
inventory_image = minetest.inventorycube("survival_hazards_waste.png");
|
||||
drawtype = "liquid";
|
||||
tiles = { "survival_hazards_waste.png" };
|
||||
special_tiles = {{
|
||||
image="survival_hazards_waste_src_anim.png",
|
||||
backface_culling=false,
|
||||
animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
|
||||
}};
|
||||
paramtype = "light",
|
||||
walkable = false;
|
||||
pointable = false;
|
||||
diggable = false;
|
||||
buildable_to = true;
|
||||
drop = "";
|
||||
liquidtype = "source";
|
||||
liquid_alternative_flowing = "survival_hazards:toxic_waste_flowing";
|
||||
liquid_alternative_source = "survival_hazards:toxic_waste_source";
|
||||
liquid_viscosity = 1;
|
||||
damage_per_second = 2;
|
||||
post_effect_color = { a=192, r=128, g=255, b=128};
|
||||
groups = { liquid=2 };
|
||||
})
|
||||
|
||||
bucket.register_liquid(
|
||||
"survival_hazards:toxic_waste_source",
|
||||
"survival_hazards:toxic_waste_flowing",
|
||||
"survival_hazards:bucket_toxic_waste",
|
||||
"survival_hazards_bucket_waste.png"
|
||||
);
|
||||
|
||||
survival.hazards.register_liquid("survival_hazards:toxic_waste", {
|
||||
damage = 2;
|
||||
});
|
|
@ -2,25 +2,28 @@
|
|||
Hunger Mod for Minetest
|
||||
This mod is part of the Survival Modpack for Minetest.
|
||||
Copyright (C) 2013 Diego Martínez <lkaezadl3@gmail.com>
|
||||
Inspired by the existing hunger mod [TODO: who's the author?]
|
||||
Inspired by the existing hunger mod by randomproof.
|
||||
|
||||
See the file `../LICENSE.txt' for information about distribution.
|
||||
|
||||
TECHNICAL NOTES
|
||||
---------------
|
||||
In order to detect if the player ate an item (to restore the hunger timer),
|
||||
this mod overrides the on_use callback of all known food items, resetting the
|
||||
timer and calling the callback to perform the actual healing. This has the
|
||||
advantage that the hunger timer is reset whenever the player "eats" the item,
|
||||
but new food items must be explicitly listed on the script for the mod to
|
||||
"know" it's food.
|
||||
This mod adds hunger to the game. The player must eat some...food...in
|
||||
order to not die.
|
||||
|
||||
At first, this was implemented by checking whether the player's HP increased
|
||||
since the last check, but this has several disadvantages:
|
||||
When hungry, the player will be hurt (and will hear a characteristc sound);
|
||||
If the player does not eat anything in a short time, he/she will be damaged
|
||||
again, and so on until he eats something or dies.
|
||||
|
||||
- It's not possible to have separate "hunger" and "thirst" if the drink
|
||||
item increases HP, because this would also be taken as eating.
|
||||
- When the HP is at max, eating a food item does not reset the hunger timer
|
||||
since there was no increase in HP.
|
||||
- Other healing mechanisms (such as the medikit blocks) may interfere with
|
||||
the detection.
|
||||
It supports lot of items, and it can detect most "food" items defined by other
|
||||
mods. Currently, it supports the apple from the default game, all the eatable
|
||||
items in rubenwardy's Food mod, and PilzAdam's farming and farming_plus mods.
|
||||
|
||||
You can also craft a meter (or gauge). It shows your current hunger by means
|
||||
of a colored bar under the item (like tool wear). Craft it like this:
|
||||
|
||||
+-------------+-------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+-------------+-------------+
|
||||
| wood planks | apple | wood planks |
|
||||
+-------------+-------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+-------------+-------------+
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
TECHNICAL NOTES ABOUT HUNGER
|
||||
----------------------------
|
||||
In order to detect if the player ate an item (to restore the hunger timer),
|
||||
this mod overrides the on_use callback of all known food items, resetting the
|
||||
timer and calling the callback to perform the actual healing. This has the
|
||||
advantage that the hunger timer is reset whenever the player "eats" the item,
|
||||
but new food items must be explicitly listed on the script for the mod to
|
||||
"know" it's food.
|
||||
|
||||
At first, this was implemented by checking whether the player's HP increased
|
||||
since the last check, but this has several disadvantages:
|
||||
|
||||
- It's not possible to have separate "hunger" and "thirst" if the drink
|
||||
item increases HP, because this would also be taken as eating.
|
||||
- When the HP is at max, eating a food item does not reset the hunger timer
|
||||
since there was no increase in HP.
|
||||
- Other healing mechanisms (such as the medikit blocks) may interfere with
|
||||
the detection.
|
|
@ -2,7 +2,6 @@
|
|||
local START_HUNGER_TIME = survival.conf_getnum("hunger.damage_start_time", 720);
|
||||
local HUNGER_TIME = survival.conf_getnum("hunger.damage_interval", 30);
|
||||
local HUNGER_DAMAGE = survival.conf_getnum("hunger.damage", 4);
|
||||
local DTIME = survival.conf_getnum("hunger.check_interval", 0.5);
|
||||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
|
@ -17,62 +16,6 @@ local timer = 0;
|
|||
|
||||
local player_state = { };
|
||||
|
||||
if (minetest.setting_getbool("enable_damage") and survival.conf_getbool("hunger.enabled", true)) then
|
||||
minetest.register_globalstep(function ( dtime )
|
||||
timer = timer + dtime;
|
||||
if (timer < DTIME) then return; end
|
||||
timer = timer - DTIME;
|
||||
for i, v in ipairs(minetest.get_connected_players()) do
|
||||
local name = v:get_player_name();
|
||||
if (not player_state[name]) then
|
||||
player_state[name] = {
|
||||
count = 0;
|
||||
hungry = false;
|
||||
next = START_HUNGER_TIME;
|
||||
};
|
||||
end
|
||||
local state = player_state[name];
|
||||
state.count = state.count + DTIME;
|
||||
if ((v:get_hp() > 0) and (state.count >= state.next)) then
|
||||
v:set_hp(v:get_hp() - HUNGER_DAMAGE);
|
||||
if (v:get_hp() <= 0) then
|
||||
minetest.chat_send_player(name, S("You died from starvation."));
|
||||
end
|
||||
state.count = state.count - state.next;
|
||||
state.next = HUNGER_TIME;
|
||||
state.hungry = true;
|
||||
minetest.sound_play({ name="survival_hunger_stomach" }, {
|
||||
pos = v:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
end
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
survival.create_meter("survival_hunger:meter", {
|
||||
description = S("Hunger Meter");
|
||||
command = {
|
||||
name = "hunger";
|
||||
label = S("Hunger");
|
||||
};
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "default:apple", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
image = "survival_hunger_meter.png";
|
||||
get_value = function ( player )
|
||||
local name = player:get_player_name();
|
||||
if (player_state[name].hungry) then
|
||||
return 0;
|
||||
else
|
||||
return 100 * (START_HUNGER_TIME - player_state[name].count) / START_HUNGER_TIME;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
-- Known food items (more suggestions are welcome)
|
||||
local known_foods = {
|
||||
|
||||
|
@ -111,23 +54,38 @@ local known_foods = {
|
|||
|
||||
};
|
||||
|
||||
-- Special sounds, in case the default one is too silly.
|
||||
local known_foods_special_sounds = {
|
||||
["food:coffee"] = "survival_hunger_sip";
|
||||
["food:milk"] = "survival_hunger_sip";
|
||||
["food:chocolate_milk"] = "survival_hunger_sip";
|
||||
["food:hotchoco"] = "survival_hunger_sip";
|
||||
["food:ms_chocolate"] = "survival_hunger_sip";
|
||||
["animalmaterials:milk"] = "survival_hunger_sip";
|
||||
};
|
||||
|
||||
local function override_on_use ( def )
|
||||
local on_use = def.on_use;
|
||||
def.on_use = function ( itemstack, user, pointed_thing )
|
||||
player_state[user:get_player_name()] = {
|
||||
count = 0;
|
||||
next = START_HUNGER_TIME;
|
||||
hungry = false;
|
||||
};
|
||||
minetest.sound_play({ name="survival_hunger_eat" }, {
|
||||
to_player = user:getpos();
|
||||
gain = 1.0;
|
||||
});
|
||||
if (on_use) then
|
||||
return on_use(itemstack, user, pointed_thing);
|
||||
else
|
||||
itemstack:take_item(1);
|
||||
return itemstack;
|
||||
local state = survival.get_player_state(user:get_player_name(), "hunger");
|
||||
if (not survival.post_event("hunger.eat", user, state)) then
|
||||
survival.reset_player_state(user:get_player_name(), "hunger");
|
||||
local soundname;
|
||||
if (known_foods_special_sounds[itemstack:get_name()]) then
|
||||
soundname = known_foods_special_sounds[itemstack:get_name()];
|
||||
else
|
||||
soundname = "survival_hunger_eat";
|
||||
end
|
||||
minetest.sound_play({ name=soundname }, {
|
||||
to_player = user:getpos();
|
||||
gain = 1.0;
|
||||
});
|
||||
if (on_use) then
|
||||
return on_use(itemstack, user, pointed_thing);
|
||||
else
|
||||
itemstack:take_item(1);
|
||||
return itemstack;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -154,19 +112,64 @@ minetest.after(1, function ( )
|
|||
|
||||
end);
|
||||
|
||||
minetest.register_on_joinplayer(function ( player )
|
||||
player_state[player:get_player_name()] = {
|
||||
count = 0;
|
||||
hungry = false;
|
||||
next = START_HUNGER_TIME;
|
||||
survival.register_state("hunger", {
|
||||
label = S("Hunger");
|
||||
item = {
|
||||
name = "survival_hunger:meter";
|
||||
description = S("Hunger Meter");
|
||||
inventory_image = "survival_hunger_meter.png";
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "default:apple", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
};
|
||||
end);
|
||||
get_default = function ( )
|
||||
return {
|
||||
count = 0;
|
||||
flag = false;
|
||||
};
|
||||
end;
|
||||
get_scaled_value = function ( state )
|
||||
if (state.flag) then
|
||||
return 0;
|
||||
else
|
||||
return 100 * (START_HUNGER_TIME - state.count) / START_HUNGER_TIME;
|
||||
end
|
||||
end;
|
||||
on_update = function ( dtime, player, state )
|
||||
state.count = state.count + dtime;
|
||||
if (state.flag and (state.count >= HUNGER_TIME)) then
|
||||
if (not survival.post_event("hunger", player, state)) then
|
||||
local hp = player:get_hp();
|
||||
state.count = 0;
|
||||
if ((hp > 0) and ((hp - HUNGER_DAMAGE) <= 0)) then
|
||||
minetest.chat_send_player(name, S("You died from starvation."));
|
||||
state.count = 0;
|
||||
state.flag = false;
|
||||
end
|
||||
player:set_hp(hp - HUNGER_DAMAGE);
|
||||
minetest.sound_play({ name="survival_hunger_stomach" }, {
|
||||
pos = player:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
end
|
||||
elseif ((not state.flag) and (state.count >= START_HUNGER_TIME)) then
|
||||
if (not survival.post_event("hunger_start", player, state)) then
|
||||
state.count = 0;
|
||||
state.flag = true;
|
||||
minetest.chat_send_player(name, S("You are hungry."));
|
||||
minetest.sound_play({ name="survival_hunger_stomach" }, {
|
||||
pos = player:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
end
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
minetest.register_on_dieplayer(function ( player )
|
||||
local name = player:get_player_name();
|
||||
player_state[name] = {
|
||||
count = 0;
|
||||
hungry = false;
|
||||
next = START_HUNGER_TIME;
|
||||
};
|
||||
survival.reset_player_state(player:get_player_name(), "hunger");
|
||||
end);
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
You died from starvation. = Has muerto de hambre.
|
||||
Hunger Meter = Medidor de Hambre
|
||||
Hunger = Hambre
|
||||
You are hungry. = Tienes hambre.
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
if (minetest.get_modpath("intllib")) then
|
||||
dofile(minetest.get_modpath("intllib").."/intllib.lua");
|
||||
S = intllib.Getter(minetest.get_current_modname());
|
||||
else
|
||||
S = function ( s ) return s; end
|
||||
end
|
||||
|
||||
local commands = { };
|
||||
|
||||
survival.register_command = function ( name, def )
|
||||
commands[name] = def;
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("survival", {
|
||||
params = S("<command> <args>...");
|
||||
description = S("Configuration of survival_lib");
|
||||
func = function ( name, param )
|
||||
local cmd = param:match("^%s*(%S+)%s*");
|
||||
if (not cmd) then
|
||||
minetest.chat_send_player(name, S("No subcommand specified."));
|
||||
return;
|
||||
end
|
||||
if (not commands[cmd]) then
|
||||
minetest.chat_send_player(name, S("Unknown subcommand `%s'."):format(cmd));
|
||||
return;
|
||||
end
|
||||
if (commands[cmd].privs) then
|
||||
local got, miss = minetest.check_player_privs(name, commands[cmd].privs);
|
||||
if (not got) then
|
||||
local text = S("Missing privileges: %s"):format(minetest.privs_to_string(miss));
|
||||
minetest.chat_send_player(name, text);
|
||||
return;
|
||||
end
|
||||
end
|
||||
local args = param:match("^%s*%S+%s+(.-)%s*$") or "";
|
||||
commands[cmd].func(name, args);
|
||||
end;
|
||||
});
|
||||
|
||||
survival.register_command("help", {
|
||||
params = S("<subcommand>");
|
||||
description = S("Get help about a subcommand");
|
||||
privs = { };
|
||||
func = function ( name, args )
|
||||
local cmd = args:match("^%s*(%S+)");
|
||||
local send = minetest.chat_send_player;
|
||||
if (cmd and (cmd ~= "")) then
|
||||
if (not commands[cmd]) then
|
||||
send(name, S("Unknown subcommand `%s'."):format(cmd));
|
||||
return;
|
||||
end
|
||||
send(name, cmd.." "..(commands[cmd].params or ""));
|
||||
send(name, " "..(commands[cmd].description or ""));
|
||||
else
|
||||
local cmds = "";
|
||||
for cmd in pairs(commands) do
|
||||
if (cmds ~= "") then cmds = cmds..", "; end
|
||||
cmds = cmds..cmd;
|
||||
end
|
||||
send(name, S("Available commands: %s"):format(cmds));
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
survival.register_command("enable", {
|
||||
params = S("<state>");
|
||||
description = S("Enable a state");
|
||||
privs = { };
|
||||
func = function ( name, args )
|
||||
local stname = args:match("^%s*(%S+)");
|
||||
local send = minetest.chat_send_player;
|
||||
if (stname and (stname ~= "")) then
|
||||
if (not survival.registered_states[stname]) then
|
||||
send(name, S("Unknown state `%s'."):format(stname));
|
||||
return;
|
||||
end
|
||||
survival.registered_states[stname].enabled = true;
|
||||
send(name, S("State `%s' enabled."):format(stname));
|
||||
else
|
||||
minetest.chat_send_player(name, S("No state specified."));
|
||||
return;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
survival.register_command("disable", {
|
||||
params = S("<state>");
|
||||
description = S("Disable a state");
|
||||
privs = { };
|
||||
func = function ( name, args )
|
||||
local stname = args:match("^%s*(%S+)");
|
||||
local send = minetest.chat_send_player;
|
||||
if (stname and (stname ~= "")) then
|
||||
if (not survival.registered_states[stname]) then
|
||||
send(name, S("Unknown state `%s'."):format(stname));
|
||||
return;
|
||||
end
|
||||
survival.registered_states[stname].enabled = true;
|
||||
send(name, S("State `%s' disabled."):format(stname));
|
||||
else
|
||||
minetest.chat_send_player(name, S("No state specified."));
|
||||
return;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
survival.register_command("state", {
|
||||
params = S("<state>");
|
||||
description = S("Get the enabled/disabled flag of a state");
|
||||
privs = { };
|
||||
func = function ( name, args )
|
||||
local stname = args:match("^%s*(%S+)");
|
||||
local send = minetest.chat_send_player;
|
||||
if (stname and (stname ~= "")) then
|
||||
if (not survival.registered_states[stname]) then
|
||||
send(name, S("Unknown state `%s'."):format(stname));
|
||||
return;
|
||||
end
|
||||
local flag = survival.registered_states[stname].enabled;
|
||||
send(name, S("State `%s' is %s."):format(stname, (flag and S("Enabled")) or S("Disabled")));
|
||||
else
|
||||
minetest.chat_send_player(name, S("No state specified."));
|
||||
return;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
survival.register_command("list", {
|
||||
params = "";
|
||||
description = S("List available states and enabled/disabled flags");
|
||||
privs = { };
|
||||
func = function ( name, args )
|
||||
local send = minetest.chat_send_player;
|
||||
for _, def in ipairs(survival.registered_states) do
|
||||
send(name, S("%s(%s): %s"):format(
|
||||
(def.label or def.name),
|
||||
def.name,
|
||||
(def.enabled and S("Enabled")) or S("Disabled")
|
||||
));
|
||||
end
|
||||
end;
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
survival = { };
|
||||
|
||||
survival.meters = { };
|
||||
local player_states = { };
|
||||
|
||||
-- Boilerplate to support localized strings if intllib mod is installed.
|
||||
local S;
|
||||
|
@ -21,82 +21,121 @@ survival.distance3d = function ( p1, p2 )
|
|||
end
|
||||
|
||||
dofile(minetest.get_modpath("survival_lib").."/config.lua");
|
||||
dofile(minetest.get_modpath("survival_lib").."/chatcmds.lua");
|
||||
|
||||
survival.create_meter = function ( name, def )
|
||||
minetest.register_tool(name, {
|
||||
description = def.description;
|
||||
inventory_image = def.image;
|
||||
on_use = def.on_use;
|
||||
});
|
||||
if (def.command and def.command.name) then
|
||||
local lbl = (def.command.label or def.command.name);
|
||||
def.command.func = function ( name, param )
|
||||
local ply = minetest.env:get_player_by_name(name);
|
||||
local val = math.floor(def.get_value(ply));
|
||||
survival.registered_states = { };
|
||||
|
||||
survival.register_state = function ( name, def )
|
||||
if (def.item) then
|
||||
if (def.item.name) then
|
||||
minetest.register_tool(def.item.name, {
|
||||
description = def.item.description or "<Unnamed item>";
|
||||
inventory_image = def.item.inventory_image;
|
||||
on_use = def.item.on_use;
|
||||
});
|
||||
if (def.item.recipe) then
|
||||
minetest.register_craft({
|
||||
output = def.item.name;
|
||||
recipe = def.item.recipe;
|
||||
});
|
||||
end
|
||||
else
|
||||
def.item = nil;
|
||||
end
|
||||
end
|
||||
if (def.command_name) then
|
||||
local lbl = (def.label or def.command_name);
|
||||
def.command_func = function ( name, param )
|
||||
local val = math.floor(def.get_scaled_value(player_states[name][def.name]));
|
||||
local val2 = math.max(0, math.min(val / 10, 10));
|
||||
minetest.chat_send_player(name, lbl..": ["..val.."%] "..string.rep("|", val2));
|
||||
end;
|
||||
minetest.register_chatcommand(def.command.name, {
|
||||
minetest.register_chatcommand(def.command_name, {
|
||||
params = "";
|
||||
description = S("Display %s"):format(lbl);
|
||||
func = def.command.func;
|
||||
});
|
||||
end
|
||||
if (def.recipe) then
|
||||
minetest.register_craft({
|
||||
output = name;
|
||||
recipe = def.recipe;
|
||||
func = def.command_func;
|
||||
});
|
||||
end
|
||||
def.name = name;
|
||||
survival.meters[name] = def;
|
||||
survival.meters[#survival.meters + 1] = def;
|
||||
survival.registered_states[name] = def;
|
||||
survival.registered_states[#survival.registered_states + 1] = def;
|
||||
end
|
||||
|
||||
survival.get_player_state = function ( name, stname )
|
||||
if (name and stname and player_states[name]) then
|
||||
return player_states[name][stname];
|
||||
else
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
survival.set_player_state = function ( name, stname, state )
|
||||
if (name and stname and state) then
|
||||
if (not player_states[name]) then
|
||||
player_states[name] = { };
|
||||
end
|
||||
player_states[name][stname] = state;
|
||||
end
|
||||
end
|
||||
|
||||
survival.reset_player_state = function ( name, stname )
|
||||
if (name and stname and survival.registered_states[stname]) then
|
||||
player_states[name][stname] = survival.registered_states[stname].get_default();
|
||||
end
|
||||
end
|
||||
|
||||
local chat_cmd_def = {
|
||||
params = "";
|
||||
description = S("Display all player stats");
|
||||
func = function ( name, param )
|
||||
for i, def in ipairs(survival.meters) do
|
||||
if (def.command and def.command.func and (not def.command.not_in_plstats)) then
|
||||
def.command.func(name, "");
|
||||
for i, def in ipairs(survival.registered_states) do
|
||||
if (not def.not_in_plstats) then
|
||||
local val = math.floor(def.get_scaled_value(player_states[name][def.name]));
|
||||
local val2 = math.max(0, math.min(val / 10, 10));
|
||||
minetest.chat_send_player(name, def.label..": ["..val.."%] "..string.rep("|", val2));
|
||||
end
|
||||
end
|
||||
end;
|
||||
};
|
||||
|
||||
minetest.register_chatcommand("plstats", chat_cmd_def);
|
||||
minetest.register_chatcommand("s", chat_cmd_def);
|
||||
|
||||
local timer = 0;
|
||||
local MAX_TIMER = 1;
|
||||
local MAX_TIMER = 0.5;
|
||||
|
||||
minetest.register_globalstep(function ( dtime )
|
||||
|
||||
timer = timer + dtime;
|
||||
if (timer < MAX_TIMER) then return; end
|
||||
|
||||
local tmr = timer;
|
||||
timer = timer - MAX_TIMER;
|
||||
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
local inv = player:get_inventory();
|
||||
for name, def in pairs(survival.meters) do
|
||||
if (def.on_step) then
|
||||
def.on_step(player);
|
||||
end
|
||||
if (survival.conf_getbool("meters_enabled", true)
|
||||
and inv:contains_item("main", ItemStack(name))) then
|
||||
for i = 1, inv:get_size("main") do
|
||||
local stack = inv:get_stack("main", i);
|
||||
if (stack:get_name() == name) then
|
||||
local value = (65533 * def.get_value(player) / 100);
|
||||
--local wear = stack:get_wear();
|
||||
inv:remove_item("main", stack);
|
||||
stack:add_wear(-65535);
|
||||
stack:add_wear(65534);
|
||||
stack:add_wear(-value);
|
||||
inv:set_stack("main", i, stack);
|
||||
break;
|
||||
local plname = player:get_player_name();
|
||||
for i, def in ipairs(survival.registered_states) do
|
||||
if (def.enabled) then
|
||||
local name = def.name;
|
||||
local state = player_states[plname][name];
|
||||
if (def.on_update) then
|
||||
def.on_update(tmr, player, state);
|
||||
end
|
||||
if (survival.conf_getbool("meters_enabled", true)
|
||||
and def.item
|
||||
and inv
|
||||
and inv:contains_item("main", ItemStack(def.item.name))) then
|
||||
for i = 1, inv:get_size("main") do
|
||||
local stack = inv:get_stack("main", i);
|
||||
if (stack:get_name() == def.item.name) then
|
||||
local value = (65533 * def.get_scaled_value(state) / 100);
|
||||
value = math.max(0, math.min(value, 65533));
|
||||
inv:remove_item("main", stack);
|
||||
stack:add_wear(-65535);
|
||||
stack:add_wear(65534);
|
||||
stack:add_wear(-value);
|
||||
inv:set_stack("main", i, stack);
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -104,3 +143,107 @@ minetest.register_globalstep(function ( dtime )
|
|||
end
|
||||
|
||||
end);
|
||||
|
||||
minetest.register_on_joinplayer(function ( player )
|
||||
local plname = player:get_player_name();
|
||||
if (not player_states[plname]) then
|
||||
player_states[plname] = { };
|
||||
end
|
||||
for i, def in ipairs(survival.registered_states) do
|
||||
local name = def.name;
|
||||
if (not player_states[plname][name]) then
|
||||
player_states[plname][name] = def.get_default();
|
||||
end
|
||||
end
|
||||
end);
|
||||
|
||||
local event_listeners = { };
|
||||
|
||||
survival.register_on_event = function ( event, func )
|
||||
if (not event_listeners[event]) then
|
||||
event_listeners[event] = { };
|
||||
end
|
||||
event_listeners[event][#event_listeners[event]] = func;
|
||||
end
|
||||
|
||||
survival.post_event = function ( event, ... )
|
||||
if (not event_listeners[event]) then return; end
|
||||
for _,func in ipairs(event_listeners[event]) do
|
||||
local r = func(...);
|
||||
if (r ~= nil) then return r; end
|
||||
end
|
||||
end
|
||||
|
||||
local STATEFILE = minetest.get_worldpath().."/survival_lib.states";
|
||||
|
||||
local function load_table ( lines, index )
|
||||
local t = { };
|
||||
while (index <= #lines) do
|
||||
local line = lines[index];
|
||||
index = index + 1;
|
||||
local c = line:sub(1, 1);
|
||||
if (c == "{") then
|
||||
local k = line:sub(2);
|
||||
t[k], index = load_table(lines, index);
|
||||
elseif (c == "}") then
|
||||
return t, index;
|
||||
elseif (c == "=") then
|
||||
line = line:sub(2);
|
||||
local p = line:find("=", 1, true);
|
||||
local k = line:sub(1, p - 1);
|
||||
local fullv = line:sub(p + 1);
|
||||
local typ = fullv:sub(1, 1);
|
||||
local v = fullv:sub(3);
|
||||
if (typ == "S") then
|
||||
-- `v' is unchanged
|
||||
elseif (typ == "N") then
|
||||
v = tonumber(v) or 0;
|
||||
elseif (typ == "B") then
|
||||
v = (v:lower() == "true");
|
||||
end
|
||||
t[k] = v;
|
||||
end
|
||||
end
|
||||
return t, index;
|
||||
end
|
||||
|
||||
local function load_states ( )
|
||||
local f = io.open(STATEFILE);
|
||||
if (not f) then return; end
|
||||
local r = { };
|
||||
local stack = { r };
|
||||
local lines = { };
|
||||
for line in f:lines() do
|
||||
lines[#lines + 1] = line;
|
||||
end
|
||||
player_states = load_table(lines, 1);
|
||||
f:close();
|
||||
end
|
||||
|
||||
local function save_table ( f, t, name )
|
||||
f:write("{"..name.."\n");
|
||||
for k, v in pairs(t) do
|
||||
if (type(v) == "table") then
|
||||
save_table(f, v, k);
|
||||
elseif (type(v) == "string") then
|
||||
f:write("="..k.."=S:"..v.."\n");
|
||||
elseif (type(v) == "number") then
|
||||
f:write("="..k.."=N:"..v.."\n");
|
||||
elseif (type(v) == "boolean") then
|
||||
f:write("="..k.."=B:"..((v and "true") or "false").."\n");
|
||||
end
|
||||
end
|
||||
f:write("}\n");
|
||||
end
|
||||
|
||||
local function save_states ( )
|
||||
local f = io.open(STATEFILE, "w");
|
||||
if (not f) then return; end
|
||||
for k, v in pairs(player_states) do
|
||||
save_table(f, v, k);
|
||||
end
|
||||
f:close();
|
||||
end
|
||||
|
||||
minetest.register_on_shutdown(save_states);
|
||||
load_states();
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
# Language: Español
|
||||
# Author: Diego Martínez <lkaezadl3@gmail.com>
|
||||
|
||||
Test Meter = Medidor de Prueba
|
||||
Display all player stats = Mostrar todas las estadisticas del jugador
|
|
@ -1,26 +1,37 @@
|
|||
|
||||
Hunger Mod for Minetest
|
||||
Thirst Mod for Minetest
|
||||
This mod is part of the Survival Modpack for Minetest.
|
||||
Copyright (C) 2013 Diego Martínez <lkaezadl3@gmail.com>
|
||||
Inspired by the existing hunger mod [TODO: who's the author?]
|
||||
|
||||
See the file `../LICENSE.txt' for information about distribution.
|
||||
|
||||
TECHNICAL NOTES
|
||||
---------------
|
||||
In order to detect if the player ate an item (to restore the hunger timer),
|
||||
this mod overrides the on_use callback of all known food items, resetting the
|
||||
timer and calling the callback to perform the actual healing. This has the
|
||||
advantage that the hunger timer is reset whenever the player "eats" the item,
|
||||
but new food items must be explicitly listed on the script for the mod to
|
||||
"know" it's food.
|
||||
This mod adds thirst to the game. The player must drink some...drink...in
|
||||
order to not die from dehydration.
|
||||
|
||||
At first, this was implemented by checking whether the player's HP increased
|
||||
since the last check, but this has several disadvantages:
|
||||
A warning will be sent to the player when thirsty, and if the player does not
|
||||
drink anything in a short time, he/she will die.
|
||||
|
||||
- It's not possible to have separate "hunger" and "thirst" if the drink
|
||||
item increases HP, because this would also be taken as eating.
|
||||
- When the HP is at max, eating a food item does not reset the hunger timer
|
||||
since there was no increase in HP.
|
||||
- Other healing mechanisms (such as the medikit blocks) may interfere with
|
||||
the detection.
|
||||
It currently supports only a few drinks: The apple juice and carrot juice from
|
||||
rubenwardy's Food mod, and a water glass defined by this mod. In all cases,
|
||||
you get an empty drinking glass after using the item (provided you have space
|
||||
in the inventory).
|
||||
|
||||
The water glass uses the drinking glass from the `vessels' mod. It can be
|
||||
obtained in these ways:
|
||||
- Crafting it with a drinking glass + a bucket of water (shapeless recipe;
|
||||
you get the bucket back), or...
|
||||
- By punching a "Source of water" (like a sink; not to be confused with
|
||||
`water_source') with an empty drinking glass. Currently supported
|
||||
sources are the Kitchen Cabinet with Sink (from homedecor), and the
|
||||
Bathroom Sink (from 3dforniture).
|
||||
|
||||
You can also craft a meter (or gauge). It shows your current thirst by means
|
||||
of a colored bar under the item (like tool wear). Craft it like this:
|
||||
|
||||
+-------------+--------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+--------------+-------------+
|
||||
| wood planks | water bucket | wood planks |
|
||||
+-------------+--------------+-------------+
|
||||
| | wood planks | |
|
||||
+-------------+--------------+-------------+
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
TECHNICAL NOTES ABOUT THIRST
|
||||
----------------------------
|
||||
Nothing to see here... keep moving... ;)
|
|
@ -14,82 +14,15 @@ end
|
|||
|
||||
local timer = 0;
|
||||
|
||||
local player_state = { };
|
||||
|
||||
if (minetest.setting_getbool("enable_damage") and survival.conf_getbool("thirst.enabled", true)) then
|
||||
print("survival_thirst: thirst enabled!");
|
||||
minetest.register_globalstep(function ( dtime )
|
||||
timer = timer + dtime;
|
||||
if (timer < DTIME) then return; end
|
||||
timer = timer - DTIME;
|
||||
for i, v in ipairs(minetest.get_connected_players()) do
|
||||
local name = v:get_player_name();
|
||||
if (not player_state[name]) then
|
||||
player_state[name] = {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
};
|
||||
end
|
||||
local state = player_state[name];
|
||||
state.count = state.count + DTIME;
|
||||
if (v:get_hp() > 0) then
|
||||
if (state.thirsty and (state.count >= PASS_OUT_TIME)) then
|
||||
state.count = 0;
|
||||
state.thirsty = false;
|
||||
v:set_hp(0);
|
||||
minetest.chat_send_player(name, S("You died from dehidration."));
|
||||
minetest.sound_play({ name="survival_thirst_pass_out" }, {
|
||||
pos = v:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
elseif ((not state.thirsty) and (state.count >= THIRST_TIME)) then
|
||||
state.count = state.count - THIRST_TIME;
|
||||
state.thirsty = true;
|
||||
minetest.sound_play({ name="survival_thirst_thirst" }, {
|
||||
pos = v:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
minetest.chat_send_player(name, S("You are thirsty."));
|
||||
end
|
||||
end
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
survival.create_meter("survival_thirst:meter", {
|
||||
description = S("Thirst Meter");
|
||||
command = {
|
||||
name = "thirst";
|
||||
label = S("Thirst");
|
||||
};
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "vessels:drinking_glass", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
image = "survival_thirst_meter.png";
|
||||
get_value = function ( player )
|
||||
local name = player:get_player_name();
|
||||
if (player_state[name].thirsty) then
|
||||
return 0;
|
||||
else
|
||||
return 100 * (THIRST_TIME - player_state[name].count) / THIRST_TIME;
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
||||
minetest.register_craftitem("survival_thirst:water_glass", {
|
||||
description = "Glass of Water";
|
||||
inventory_image = "survival_thirst_water_glass.png";
|
||||
groups = { drink=1; survival_no_override=1; };
|
||||
stack_max = 10;
|
||||
on_use = function ( itemstack, user, pointed_thing )
|
||||
player_state[user:get_player_name()] = {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
};
|
||||
local state = survival.get_player_state(user:get_player_name(), "thirst");
|
||||
state.count = 0;
|
||||
state.thirsty = false;
|
||||
minetest.sound_play({ name="survival_thirst_drink" }, {
|
||||
to_player = user:getpos();
|
||||
gain = 1.0;
|
||||
|
@ -113,10 +46,10 @@ minetest.register_on_punchnode(function ( pos, node, puncher )
|
|||
local item = puncher:get_wielded_item();
|
||||
if ((item:get_name() == "vessels:drinking_glass")
|
||||
and alt_water_sources[node.name]) then
|
||||
local newitem = ItemStack("survival_thirst:water_glass");
|
||||
local newitem = ItemStack("survival_thirst:water_glass 1");
|
||||
local inv = puncher:get_inventory();
|
||||
if (inv:room_for_item("main", newitem)) then
|
||||
inv:remove_item("main", item);
|
||||
inv:remove_item("main", ItemStack(item:get_name().." 1"));
|
||||
inv:add_item("main", newitem);
|
||||
end
|
||||
end
|
||||
|
@ -148,10 +81,7 @@ local known_drinks = {
|
|||
local function override_on_use ( def )
|
||||
local on_use = def.on_use;
|
||||
def.on_use = function ( itemstack, user, pointed_thing )
|
||||
player_state[user:get_player_name()] = {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
};
|
||||
local state = survival.get_player_state(user:get_player_name(), "thirst");
|
||||
minetest.sound_play({ name="survival_thirst_drink" }, {
|
||||
to_player = user:getpos();
|
||||
gain = 1.0;
|
||||
|
@ -187,16 +117,57 @@ minetest.after(1, function ( )
|
|||
|
||||
end);
|
||||
|
||||
minetest.register_on_joinplayer(function ( player )
|
||||
player_state[player:get_player_name()] = {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
survival.register_state("thirst", {
|
||||
label = S("Thirst");
|
||||
item = {
|
||||
name = "survival_thirst:meter";
|
||||
description = S("Thirst Meter");
|
||||
inventory_image = "survival_thirst_meter.png";
|
||||
recipe = {
|
||||
{ "", "default:wood", "" },
|
||||
{ "default:wood", "vessels:drinking_glass", "default:wood" },
|
||||
{ "", "default:wood", "" },
|
||||
};
|
||||
};
|
||||
end);
|
||||
|
||||
minetest.register_on_dieplayer(function ( player )
|
||||
player_state[player:get_player_name()] = {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
};
|
||||
end);
|
||||
get_default = function ( )
|
||||
return {
|
||||
count = 0;
|
||||
thirsty = false;
|
||||
};
|
||||
end;
|
||||
get_scaled_value = function ( state )
|
||||
if (state.thirsty) then
|
||||
return 0;
|
||||
else
|
||||
return 100 * (THIRST_TIME - state.count) / THIRST_TIME;
|
||||
end
|
||||
end;
|
||||
on_update = function ( dtime, player, state )
|
||||
if (player:get_hp() > 0) then
|
||||
state.count = state.count + dtime;
|
||||
local name = player:get_player_name();
|
||||
if (state.thirsty and (state.count >= PASS_OUT_TIME)) then
|
||||
state.count = 0;
|
||||
state.thirsty = false;
|
||||
if (player:get_hp() > 0) then
|
||||
minetest.chat_send_player(name, S("You died from dehydration."));
|
||||
end
|
||||
player:set_hp(0);
|
||||
minetest.sound_play({ name="survival_thirst_pass_out" }, {
|
||||
pos = player:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
elseif ((not state.thirsty) and (state.count >= THIRST_TIME)) then
|
||||
state.count = 0;
|
||||
state.thirsty = true;
|
||||
minetest.sound_play({ name="survival_thirst_thirst" }, {
|
||||
pos = player:getpos();
|
||||
gain = 1.0;
|
||||
max_hear_distance = 16;
|
||||
});
|
||||
minetest.chat_send_player(name, S("You are thirsty."));
|
||||
end
|
||||
end
|
||||
end;
|
||||
});
|
||||
|
|
After Width: | Height: | Size: 283 B |
After Width: | Height: | Size: 249 B |
|
@ -0,0 +1,35 @@
|
|||
|
||||
[b]Survival Mod for Minetest[/b]
|
||||
|
||||
This mod adds new hazards to the survival aspect of the game.
|
||||
Currently, this adds hunger, thirst, and drowning. It's planned to add
|
||||
stamina (tiredness), and temperature in the future.
|
||||
|
||||
See the file `LICENSE.txt' for information about distribution.
|
||||
|
||||
The survival_drowning mod is a modified version of the drowning mod by [who?].
|
||||
The survival_hunger mod was written from scratch inspired by the existing
|
||||
hunger mod by [who?].
|
||||
|
||||
Download: https://github.com/kaeza/minetest-survival_modpack/archive/master.zip
|
||||
Github Repo: https://github.com/kaeza/minetest-survival_modpack
|
||||
|
||||
[b]Installing[/b]
|
||||
|
||||
Unpack the modpack into one of the directories where Minetest looks for mods.
|
||||
For more information, see http://wiki.minetest.com/wiki/Installing_mods
|
||||
|
||||
See the respective mods' README.txt for information about technical aspects
|
||||
of the implementation and known bugs.
|
||||
|
||||
[b]Configuring[/b]
|
||||
|
||||
The mod can be configured by means of a `survival_lib.conf' file. This file
|
||||
is read first from the survival_lib mod's directory, and then from the
|
||||
current world directory (so you can have different settings per world).
|
||||
Options set in `survival_lib/survival_lib.conf' override the defaults, and
|
||||
those set in `<worlddir>/survival_lib.conf' override these, so you can
|
||||
specify global defaults in the mod dir, and override those settings in the
|
||||
world dir.
|
||||
|
||||
See `survival_lib.conf.example' for an example of what can be configured.
|