Initial commit

master
Diego Martínez 2013-03-01 03:45:13 -02:00
parent 3d23251090
commit 23f205e12e
28 changed files with 942 additions and 411 deletions

59
API.txt
View File

@ -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.

20
CHANGES.txt Normal file
View File

@ -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.

View File

@ -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

View File

@ -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 | |
+-------------+-------------+-------------+

View File

@ -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.

View File

@ -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);

View File

@ -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.

View File

@ -0,0 +1,3 @@
default
bucket
survival_lib

131
survival_hazards/init.lua Normal file
View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -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;
});

View File

@ -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 | |
+-------------+-------------+-------------+

View File

@ -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.

View File

@ -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);

View File

@ -5,3 +5,4 @@
You died from starvation. = Has muerto de hambre.
Hunger Meter = Medidor de Hambre
Hunger = Hambre
You are hungry. = Tienes hambre.

145
survival_lib/chatcmds.lua Normal file
View File

@ -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;
});

View File

@ -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();

View File

@ -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

View File

@ -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 | |
+-------------+--------------+-------------+

View File

@ -0,0 +1,4 @@
TECHNICAL NOTES ABOUT THIRST
----------------------------
Nothing to see here... keep moving... ;)

View File

@ -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;
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

35
topic.txt Normal file
View File

@ -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.