Compare commits
6 commits
282128382d
...
96d2be9ebd
Author | SHA1 | Date | |
---|---|---|---|
|
96d2be9ebd | ||
|
9712c7999c | ||
|
19ab4c4a70 | ||
|
038dd3c1e5 | ||
|
e2f2ba9fae | ||
|
055be8c9e5 |
7 changed files with 156 additions and 100 deletions
|
@ -91,11 +91,14 @@ end
|
||||||
local function set_max_hp(player, max_hp)
|
local function set_max_hp(player, max_hp)
|
||||||
local cur_hp = player:get_hp()
|
local cur_hp = player:get_hp()
|
||||||
local old_max = player:get_properties().hp_max
|
local old_max = player:get_properties().hp_max
|
||||||
local new_hp = cur_hp + max_hp - old_max
|
|
||||||
player:set_properties({
|
|
||||||
hp_max = max_hp
|
|
||||||
})
|
|
||||||
|
|
||||||
|
if old_max == 0 then
|
||||||
|
minetest.log("error", "[ctf_classes] Reviving dead player " .. player:get_player_name())
|
||||||
|
end
|
||||||
|
|
||||||
|
player:set_properties({hp_max = max_hp})
|
||||||
|
|
||||||
|
local new_hp = cur_hp + max_hp - old_max
|
||||||
if new_hp > max_hp then
|
if new_hp > max_hp then
|
||||||
minetest.log("error", string.format("New hp %d is larger than new max %d, old max is %d", new_hp, max_hp, old_max))
|
minetest.log("error", string.format("New hp %d is larger than new max %d, old max is %d", new_hp, max_hp, old_max))
|
||||||
new_hp = max_hp
|
new_hp = max_hp
|
||||||
|
|
|
@ -10,8 +10,10 @@ local items = {
|
||||||
"* Use medkits to replenish health gradually.",
|
"* Use medkits to replenish health gradually.",
|
||||||
"* Gain more score by killing more than you die, or by capturing the flag.",
|
"* Gain more score by killing more than you die, or by capturing the flag.",
|
||||||
"* Players are immune for 5 seconds after they respawn.",
|
"* Players are immune for 5 seconds after they respawn.",
|
||||||
"* Access the pro section of the chest by achieving 2k+ score,",
|
"* Access the pro section of the chest by achieving 10k+ score and either",
|
||||||
" killing 3 people for every 2 deaths, and capturing the flag at least 10 times",
|
" killing 3 people for every 2 deaths and capturing the flag at least 10 times",
|
||||||
|
" or killing as many people as you die, capturing the flag on every 3rd attempt",
|
||||||
|
" and at least 30 times.",
|
||||||
"",
|
"",
|
||||||
|
|
||||||
color .. "Team Co-op",
|
color .. "Team Co-op",
|
||||||
|
@ -30,12 +32,12 @@ local items = {
|
||||||
|
|
||||||
color .. "Contact Moderators",
|
color .. "Contact Moderators",
|
||||||
"",
|
"",
|
||||||
"* Report people using /report or the #reports channel in Discord",
|
"* Report people using /report.",
|
||||||
"",
|
"",
|
||||||
|
|
||||||
color .. "Other",
|
color .. "Other",
|
||||||
"",
|
"",
|
||||||
"* Capture The Flag Discord: https://discord.gg/vcZTRPX",
|
"* Capture The Flag Discord (not related to this server): https://discord.gg/vcZTRPX",
|
||||||
}
|
}
|
||||||
for i = 1, #items do
|
for i = 1, #items do
|
||||||
items[i] = minetest.formspec_escape(items[i])
|
items[i] = minetest.formspec_escape(items[i])
|
||||||
|
|
|
@ -1,88 +1,84 @@
|
||||||
# CTF Map - Map maker
|
# CTF map-maker mod
|
||||||
|
|
||||||
## Creating a new map
|
## Making a new map
|
||||||
|
|
||||||
### Youtube tutorial
|
### Youtube Tutorial
|
||||||
https://youtu.be/orBsC9wViUw
|
https://youtu.be/orBsC9wViUw
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- Minetest 5.0.0 or later (https://minetest.net/)
|
||||||
|
- Minetest Game (https://github.com/minetest/minetest_game/) (CTF supports most MTG nodes)
|
||||||
|
- `ctf_map` modpack (copy this folder to `minetest/mods`)
|
||||||
|
- `worldedit` modpack (WE) (https://content.minetest.net/packages/sfan5/worldedit/)
|
||||||
|
|
||||||
### 1. Dependencies
|
### Find an area
|
||||||
|
|
||||||
* Minetest 5.0.0 or later.
|
- The area can be maximum 230x230 blocks in surface area, but it can be lesser.
|
||||||
* `ctf_map` modpack (by copying the folder from this game to `minetest/mods`)
|
- Modify the area to *your* unique ctf_map
|
||||||
* `worldedit` and `worldedit_commands`.
|
- you could add
|
||||||
|
- buildings
|
||||||
|
- lakes
|
||||||
|
- hills
|
||||||
|
- etc.
|
||||||
|
- If you haven't modified the map at all, do the following to speed up barrier placement:
|
||||||
|
|
||||||
### 2. Find an area
|
- Stop Minetest.
|
||||||
|
- Open up the world's world.mt
|
||||||
|
- Set backend to "dummy".
|
||||||
|
- Save.
|
||||||
|
|
||||||
* Can use Minetest Game and any mapgen.
|
### The `gui`window
|
||||||
* It must be a cube, and the barrier will be in the exact center.
|
|
||||||
* It should be around 230x230 in surface area, but this can be lesser.
|
|
||||||
* Feel free to modify the area to your needs.
|
|
||||||
|
|
||||||
### 3. Select the area
|
![gui-window](./gui.png)
|
||||||
|
|
||||||
There are multiple ways do this, this is the simplest in most cases.
|
There are many ways of placing the barrier:
|
||||||
|
|
||||||
* If you haven't modified the map at all, do the following to speed up barrier placement:
|
- Go to the center of the map and click on `Player Pos` and then on `To WE`
|
||||||
* Stop Minetest.
|
- set a radius and a height for the map
|
||||||
* Open up the world's world.mt
|
- **Or** select the area of the map via WE
|
||||||
* Set backend to "dummy".
|
- Go to one corner of the map and type `//pos 1` in the chat
|
||||||
* Save.
|
- Then go to the opposite corner of the cube and type `//pos 2` in the chat
|
||||||
* Using worldedit, select the area.
|
- Click on `From WE` to import the positions
|
||||||
* Type `/gui`, and click `Player pos` then `From WE` and then `To WE`.
|
- **If `h` is negative change it to the positive number** (`-130 -> 130`)
|
||||||
* Check that the center location is the right place for the barrier to go.
|
- **Both radii must be the same!**
|
||||||
* Check that the bounds extend far enough.
|
- The rotation of the map has to be `z=0` (currently x=0 creates bugs and errors)
|
||||||
|
- Click on `Place Barriers` (Note that this has no undo)
|
||||||
|
- After the barriers are placed, click on `Givme Flags` to get 2 flags and place them at the bases.
|
||||||
|
|
||||||
### 4. Place barriers
|
### Meta Data
|
||||||
|
|
||||||
* The barrier is a plane defined by co-ordinate (=0).
|
The `gui`window only shows the most important things. You have to add the missing in the `map.conf` later.
|
||||||
* If you choose `X=0` the barrier will be placed having the X co-ordinate as 0. But from a few months, the `X=0` co-ordinate creates bugs and errors. It's better if you choose `Z=0` for creating your map.
|
|
||||||
* If you choose `Z=0` The barrier will be placed having the Z co-ordinate as 0.
|
|
||||||
* Click "place barrier". Note that this command does not have an undo.
|
|
||||||
* After placing barriers you should place 2 flags where you want bases to be. You get flags in `/gui` --> `Giveme flags`
|
|
||||||
|
|
||||||
### 5. Meta data
|
### Exporting
|
||||||
|
|
||||||
* Set the meta data
|
- Click on `Export` to export the map-files. This may takes some time
|
||||||
|
|
||||||
### 6. Export
|
## Map Meta
|
||||||
|
|
||||||
* Click export, and wait until completion.
|
The metadata of each map are stored in the `map.conf` file and includes all important information about them:
|
||||||
* Copy the resultant folder from `worlddir/schems/` into `games/capturetheflag/mods/ctf/ctf_map/ctf_map_core/maps/`.
|
- `name`: Name of the map.
|
||||||
* Profit!
|
- `author`: Author of the map.
|
||||||
|
- `hint` [Optional]: A helpful tip for players to understand unique maps.
|
||||||
|
- `roation`: The rotation of the map. [x|y]
|
||||||
|
- `r`: Radius of the map.
|
||||||
|
- `h`: Heigt of the map (**If it's an odd numer, make h=h+1 `107->108`**).
|
||||||
|
- `team.i`: Name of the team.
|
||||||
|
- `team.i.color`: Color of the team.
|
||||||
|
- `team.i.pos`: Position of team `i`'s flag, relative ot the center of schem. **The y-positions of the flags must be an integer!** `30,-32.5,60 -> 30,-33,60`
|
||||||
|
- `chest.i.from` and `chests.i.to` [Optional]: Positions of diagonal corners of custom chest zone `i`, relative to the center of the schem.
|
||||||
|
- `chests.i.n` [Optional]: Number if chests in zone `i`
|
||||||
|
- `license`: Name of license of the map.
|
||||||
|
- `other` [Optional]: Additional information about the map. This is displayed in the maps catalog.
|
||||||
|
- `base_node` [Optional]: String of the node around the flags.
|
||||||
|
- `initial_stuff` [Optional]: Comma-separated list of itemstacks to be given to the player on join and on respawn.
|
||||||
|
- `treasures` [Optional]: List of treasures to be registered for the map, in a serialized format. Refer to the `treasures` sub-section for more details.
|
||||||
|
- `start_time` [Optional]: Time of day when the match starts. Default to `0.4` [`0 - 1`].
|
||||||
|
- `time_speed` [Optional]: Time speed multiplier. Accepts any valid number. Defaults to 1.
|
||||||
|
- `phys_speed` [Optional]: Player speed multiplier. Accepts any valid number. Defaults to 1.
|
||||||
|
- `phys_jump` [Optional]: Player jump multiplier. Accepts any valid number. Defaults to 1.
|
||||||
|
- `phys_gravity` [Optional]: Player gravity multiplier. Accepts any valid number. Defaults to 1.
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### Map meta
|
|
||||||
|
|
||||||
Each map's metadata is stored in an accompanying `map.conf` file containing the following data:
|
|
||||||
|
|
||||||
* `name`: Name of map.
|
|
||||||
* `author`: Author of the map.
|
|
||||||
* `hint`: [Optional] Helpful hint or tip for unique maps, to help players understand the map.
|
|
||||||
* `rotation`: Rotation of the schem. [`x`|`z`]
|
|
||||||
* `r`: Radius of the map.
|
|
||||||
* `h`: Height of the map.
|
|
||||||
* `team.i`: Name of team `i`.
|
|
||||||
* `team.i.color`: Color of team `i`.
|
|
||||||
* `team.i.pos`: Position of team `i`'s flag, relative to center of schem.
|
|
||||||
* `chests.i.from`, `chests.i.to`: [Optional] Positions of diagonal corners of custom chest
|
|
||||||
zone `i`, relative to the center of the schem.
|
|
||||||
* `chests.i.n`: [Optional] Number of chests to place in custom chest zone `i`.
|
|
||||||
* `license`: Name of the license of the map.
|
|
||||||
* `other`: [Optional] Misc. information about the map. This is displayed in the maps catalog.
|
|
||||||
* `base_node`: [Optional] Technical name of node to be used for the team base.
|
|
||||||
* `initial_stuff`: [Optional] Comma-separated list of itemstacks to be given to the player
|
|
||||||
on join and on respawn.
|
|
||||||
* `treasures`: [Optional] List of treasures to be registered for the map, in a serialized
|
|
||||||
format. Refer to the `treasures` sub-section for more details.
|
|
||||||
* `start_time`: [Optional] Time at start of match. Defaults to `0.4` [`0` - `1`].
|
|
||||||
* `time_speed`: [Optional] Time speed multiplier. Accepts any valid number. Defaults to 1.
|
|
||||||
* `phys_speed`: [Optional] Player speed multiplier. Accepts any valid number. Defaults to 1.
|
|
||||||
* `phys_jump`: [Optional] Player jump multiplier. Accepts any valid number. Defaults to 1.
|
|
||||||
* `phys_gravity`: [Optional] Player gravity multiplier. Accepts any valid number. Defaults to 1.
|
|
||||||
|
|
||||||
#### `license`
|
#### `license`
|
||||||
|
|
||||||
* Every map must have its own license. Once you've chosen your license, simply add the following line to the `map.conf` file:
|
* Every map must have its own license. Once you've chosen your license, simply add the following line to the `map.conf` file:
|
||||||
|
|
BIN
mods/ctf/ctf_map/map_maker/gui.png
Normal file
BIN
mods/ctf/ctf_map/map_maker/gui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
|
@ -37,13 +37,18 @@ minetest.register_on_mods_loaded(function()
|
||||||
table.insert(minetest.registered_on_respawnplayers, 1, function(player)
|
table.insert(minetest.registered_on_respawnplayers, 1, function(player)
|
||||||
local pname = player:get_player_name()
|
local pname = player:get_player_name()
|
||||||
|
|
||||||
if ctf_respawn_delay.players[pname] and ctf_respawn_delay.players[pname].timeleft == "waiting" then
|
if ctf_respawn_delay.players[pname] then
|
||||||
ctf_respawn_delay.players[pname].timeleft = RESPAWN_DELAY
|
-- Since the player is still dead the client can send respawn actions
|
||||||
local pos = player:get_pos()
|
-- https://github.com/minetest/minetest/blob/4152227f17315a9cf9038266d9f9bb06e21e3424/src/network/serverpackethandler.cpp#L895
|
||||||
pos.y = ctf_map.map.h/2 + 10
|
-- We should ignore those
|
||||||
|
if ctf_respawn_delay.players[pname].timeleft == "waiting" then
|
||||||
|
ctf_respawn_delay.players[pname].timeleft = RESPAWN_DELAY
|
||||||
|
local pos = player:get_pos()
|
||||||
|
pos.y = ctf_map.map.h/2 + 10
|
||||||
|
|
||||||
player:set_pos(pos) -- Player will be stuck there because CTF 'air' is walkable
|
player:set_pos(pos) -- Player will be stuck there because CTF 'air' is walkable
|
||||||
minetest.after(RESPAWN_INTERVAL, respawnfunc, pname)
|
minetest.after(RESPAWN_INTERVAL, respawnfunc, pname)
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -56,15 +61,10 @@ minetest.register_on_mods_loaded(function()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
function ctf_respawn_delay.respawnplayer(name)
|
function respawnplayer(player, pname)
|
||||||
local player = minetest.get_player_by_name(name)
|
player:hud_remove(ctf_respawn_delay.players[pname].hudid)
|
||||||
|
player:set_properties({hp_max = ctf_respawn_delay.players[pname].old_max})
|
||||||
if not player then return end
|
player:set_hp(ctf_respawn_delay.players[pname].old_max)
|
||||||
|
|
||||||
player:hud_remove(ctf_respawn_delay.players[name].hudid)
|
|
||||||
player:set_properties({hp_max = ctf_respawn_delay.players[name].old_max})
|
|
||||||
player:set_hp(ctf_respawn_delay.players[name].old_max)
|
|
||||||
ctf_respawn_delay.players[name] = nil
|
|
||||||
|
|
||||||
for k, func in ipairs(ctf_respawn_delay.registered_on_respawnplayers) do
|
for k, func in ipairs(ctf_respawn_delay.registered_on_respawnplayers) do
|
||||||
func(player)
|
func(player)
|
||||||
|
@ -72,16 +72,13 @@ function ctf_respawn_delay.respawnplayer(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
function respawnfunc(pname)
|
function respawnfunc(pname)
|
||||||
local player = minetest.get_player_by_name(pname)
|
if not ctf_respawn_delay.players[pname] then
|
||||||
|
|
||||||
if not player or not ctf_respawn_delay.players[pname] then
|
|
||||||
ctf_respawn_delay.players[pname] = nil
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(ctf_respawn_delay.players[pname].timeleft) == "string" then
|
local player = minetest.get_player_by_name(pname)
|
||||||
minetest.after(RESPAWN_INTERVAL, respawnfunc, pname)
|
if not player then
|
||||||
|
ctf_respawn_delay.players[pname] = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -93,12 +90,26 @@ function respawnfunc(pname)
|
||||||
|
|
||||||
minetest.after(RESPAWN_INTERVAL, respawnfunc, pname)
|
minetest.after(RESPAWN_INTERVAL, respawnfunc, pname)
|
||||||
else
|
else
|
||||||
ctf_respawn_delay.respawnplayer(pname)
|
respawnplayer(player, pname)
|
||||||
|
ctf_respawn_delay.players[pname] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ctf_match.register_on_new_match(function()
|
ctf_match.register_on_new_match(function()
|
||||||
for name in pairs(ctf_respawn_delay.players) do
|
for pname in pairs(ctf_respawn_delay.players) do
|
||||||
ctf_respawn_delay.respawnplayer(name)
|
local player = minetest.get_player_by_name(pname)
|
||||||
|
if player then
|
||||||
|
respawnplayer(player, pname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ctf_respawn_delay.players = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
minetest.register_on_leaveplayer(function(player)
|
||||||
|
local pname = player:get_player_name()
|
||||||
|
if ctf_respawn_delay.players[pname] then
|
||||||
|
player:set_properties({hp_max = ctf_respawn_delay.players[pname].old_max})
|
||||||
|
ctf_respawn_delay.players[pname] = nil
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -259,3 +259,48 @@ minetest.register_chatcommand("makepro", {
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("makecapturepro", {
|
||||||
|
params = "[player_name]",
|
||||||
|
description = "Make player a 'capture pro'",
|
||||||
|
privs = {ctf_admin = true},
|
||||||
|
func = function(name, param)
|
||||||
|
-- Check if param is specified, else target the caller
|
||||||
|
param = param:trim()
|
||||||
|
if param == "" then
|
||||||
|
param = name
|
||||||
|
end
|
||||||
|
|
||||||
|
local modified = false
|
||||||
|
local stats = ctf_stats.player(param)
|
||||||
|
|
||||||
|
local deaths = math.max(stats.deaths, 1)
|
||||||
|
if stats.kills < 1.0 * deaths then
|
||||||
|
stats.kills = math.ceil(1.01 * deaths)
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if stats.score < 10000 then
|
||||||
|
stats.score = 10000
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if stats.captures < 30 then
|
||||||
|
stats.captures = 30
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local attempts = math.max(stats.attempts, 1)
|
||||||
|
if stats.captures < 0.33 * attempts then
|
||||||
|
stats.captures = math.ceil(0.34 * attempts)
|
||||||
|
modified = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if modified then
|
||||||
|
ctf_stats.request_save()
|
||||||
|
return true, "Made " .. param .. " a pro!"
|
||||||
|
else
|
||||||
|
return false, param .. " is already a pro!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
|
@ -578,7 +578,6 @@ minetest.register_node("default:clay", {
|
||||||
description = "Clay",
|
description = "Clay",
|
||||||
tiles = {"default_clay.png"},
|
tiles = {"default_clay.png"},
|
||||||
groups = {crumbly = 3},
|
groups = {crumbly = 3},
|
||||||
drop = 'default:clay_lump 4',
|
|
||||||
sounds = default.node_sound_dirt_defaults(),
|
sounds = default.node_sound_dirt_defaults(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue