Improve the map editor (#1018)

master
Zardshard 2022-08-09 14:04:29 -04:00 committed by GitHub
parent e95cdf59c2
commit f1517b0b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 139 deletions

View File

@ -12,7 +12,7 @@ globals = {
"dropondie", "grenades",
"chatcmdbuilder", "crafting", "hpbar", "playertag", "random_messages",
"skybox", "throwable_snow",
"skybox", "throwable_snow", "worldedit",
"default", "doors", "player_api", "sfinv", "binoculars",

View File

@ -2,85 +2,122 @@
## Creating a new map
### Youtube tutorial
### 1. Dependencies
- Minetest 5.0.0 or later (https://minetest.net/)
- `Capture the Flag` game (https://content.minetest.net/packages/rubenwardy/capturetheflag/)
- *Optional:* `worldedit` (https://content.minetest.net/packages/sfan5/worldedit/)
### 2. Create the world
### 2. Find an area
1. Select the `Capture the Flag` subgame.
2. Create a new world. You can select any mapgen you like.
3. Enable creative mode. This will enable `mapedit` mode.
4. *Optional:* Enable `worldedit`
### 3. Select an area
1. Decide where you will build your map. The area can be maximum 230x230 blocks in surface area, but it can be lesser.
2. Grant yourself the `ctf_map_editor` privelege by running `/grantme ctf_map_editor`
3. Run `/ctf_map editor`
4. Select "Create New Map"
5. Select a position to be one corner of the map.
6. Select a position on the opposite corner. Place it higher/lower than the first one to give the map height.
### 3. Select the area
**Note:** you can change your area at any time by typing `/ctf_map editor` and pressing the "Corners" button.
### 4. Build
- You could add
- buildings
- lakes
- hills
- etc.
- Many blocks have an indestructable variant
- Don't forget to add
- Team chests
- Flags. Place it, then right-click it to choose flag color.
- "Indestructable Barrier Glass" around the edge of the world.
- "Indestructable Red Barrier Glass" for the build-time wall.
- "Indestructable Red Barrier Stone" for underground build-time wall.
### 4. Place barriers
### 5. Fill out information about the map
Run `/ctf_map editor`.
An explanation of some of the fields is given below.
#### Map Enabled
Whether or not the map is available for play. You will want to check this.
### 5. Meta data
### 6. Export
## Documentation
### Map meta
#### `license`
* Every map must have its own license. Once you've chosen your license, <...>
#### License
* Every map must have its own license.
* If attribution is required (for example if you modify other's map and you have to tell who is author of the original map), that has to be appended to the `license` field.
If you want to tell more infomation, you can use:
If you want to tell more infomation, you can use the `Other info` field.
* If you don't know which license to use, [this list of CC licenses](https://creativecommons.org/use-remix/cc-licenses/) can help you.
* We can only accept Free Software licenses, e.g.`CC BY-SA 4.0`.
* Please know what you are doing when choosing a certain license. For example, you can read information about various licenses and/or consult a lawyer.
#### Map Hint
Does your map have hidden treasures? You can hint about them with the hint field.
#### `treasures`
#### Treasures
A list of treasures that could end up in treasure chests
Format:
```
[name];[min_count];[max_count];[max_stacks];[rarity];[TREASURE_VERSION];
```
#### `initial_stuff`
* `rarity` is a value between 0 and 1 specifying the probability it will be added to a chest.
* `TREASURE_VERSION` should currently be set to one.
Example:
```
default:lava_source;1;10;1;0.2;1;default:water_source;1;10;1;0.2;1;
```
#### Map Initial Stuff
`initial_stuff` are the items given to players at their (re)spawn. The `initial_stuff` field is located in the `map.conf` file. At least a pickaxe and some torches should be given in the map's `initial_stuff`.
An example of `initial_stuff` value that registers a stone pickaxe, 30 cobblestones, 5 torches and a pistol is given below.
```properties
initial_stuff = default:pick_stone,default:cobble 30,default:torch 5,ctf_ranged:pistol_loaded
```
default:pick_stone,default:cobble 30,default:torch 5,ctf_ranged:pistol_loaded
```
### `screenshot`
#### Map start_time
What time of the day the match begins at. Changing this field will instantly update the time of day in the world you are editing.
Every map must have its own screenshot in map's folder. It should have an aspect ratio of 3:2 (screenshot 600x400px is suggested).
* `0` is for midnight
* `1000` is for 1 AM
* `2000` is for 2 AM
* etc.
#### Zone Bounds
How far the players of a certain team may go during build time.
**Note:** Even standing on the boundary sends the player back. They do not have to go beyond it. Place the boundaries accordingly.
#### Chest Zones
Areas where chests can be placed. Some maps allow placing chests anywhere, while others only place them in certain locations.
### 6. Export
1. Press "Finish Editing"
2. Find the exported map
3. Move to \[Minetest folder]/games/capturetheflag/mods/ctf/ctf_map/maps
### 7. Play
1. Create a new world with the `singlenode` mapgen.
2. Make sure creative mode is disabled
3. Grant yourself `ctf_admin` by issuing `/grantme ctf_admin`
4. Type `/maps`
5. Select your map
6. Press "Skip to map"
### 8. Screenshot
If you choose to submit your map, include a screenshot of it in the exported map's folder. It should have an aspect ratio of 3:2 (screenshot 600x400px is suggested).
It should be named `screenshot.png`.
### `skybox` [Optional]
Six images which should be in map's folder (in a `skybox` folder).
* `skybox/Up.png` - up
* `skybox/Down.png` - down
* `skybox/Front.png` - east
* `skybox/Back.png` - west
* `skybox/Left.png` - south
* `skybox/Right.png` - north
You have to include skybox license in your `license` field. We can only accept Free Software licenses, e.g. `CC0`, `CC BY 3.0`, `CC BY 4.0`, `CC BY-SA 3.0`, `CC BY-SA 4.0`.
Before you test your skybox images in local CTF game, run the `update.sh` file in the `games/capturetheflag/` folder.
You can find some good skyboxes with suitable licenses at [opengameart.org](https://opengameart.org/art-search-advanced?field_art_tags_tid=skybox) or [www.humus.name](https://www.humus.name/index.php?page=Textures).
## Editing exported map

View File

@ -2,19 +2,40 @@ local getpos_players = {}
function ctf_map.get_pos_from_player(name, amount, donefunc)
getpos_players[name] = {amount = amount, func = donefunc, positions = {}}
minetest.chat_send_player(name, "Please punch a node or run `/ctf_map here` to supply coordinates")
if amount == 2 and minetest.get_modpath("worldedit") then
worldedit.pos1[name] = nil
worldedit.pos2[name] = nil
worldedit.marker_update(name)
getpos_players[name].place_markers = true
end
minetest.chat_send_player(name, minetest.colorize(ctf_map.CHAT_COLOR,
"Please punch a node or run `/ctf_map here` to supply coordinates"))
end
local function add_position(player, pos)
pos = vector.round(pos)
table.insert(getpos_players[player].positions, pos)
minetest.chat_send_player(player, "Got pos "..minetest.pos_to_string(pos, 1))
minetest.chat_send_player(player, minetest.colorize(ctf_map.CHAT_COLOR,
"Got pos "..minetest.pos_to_string(pos, 1)))
if getpos_players[player].place_markers then
if #getpos_players[player].positions == 1 then
worldedit.pos1[player] = pos
worldedit.mark_pos1(player)
elseif #getpos_players[player].positions == 2 then
worldedit.pos2[player] = pos
worldedit.mark_pos2(player)
end
end
if getpos_players[player].amount > 1 then
getpos_players[player].amount = getpos_players[player].amount - 1
else
minetest.chat_send_player(player, "Done getting positions!")
minetest.chat_send_player(player, minetest.colorize(ctf_map.CHAT_COLOR,
"Done getting positions!"))
getpos_players[player].func(player, getpos_players[player].positions)
getpos_players[player] = nil
end

View File

@ -1,7 +1,7 @@
if not ctf_core.settings.server_mode or ctf_core.settings.server_mode == "play" then
assert(
minetest.get_mapgen_setting("mg_name") == "singlenode",
"You must use singlenode mapgen when ctf_server_mode is set to 'play'"
"If you create a map, you must enable creative mode. If you want to play, you must use the singlenode mapgen."
)
end
@ -89,7 +89,8 @@ minetest.register_chatcommand("ctf_map", {
if ctf_core.settings.server_mode ~= "mapedit" then
minetest.chat_send_player(name,
minetest.colorize("red", "It is not recommended to edit maps unless the server is in mapedit mode"))
minetest.colorize("red", "It is not recommended to edit maps unless the server is in mapedit mode\n"..
"To enable mapedit mode, enable creative mode."))
end
ctf_map.show_map_editor(name)

View File

@ -1,4 +1,5 @@
local CURRENT_MAP_VERSION = "2"
local modname = minetest.get_current_modname();
function ctf_map.skybox_exists(subdir)
local list = minetest.get_dir_list(subdir, true)
@ -265,6 +266,9 @@ function ctf_map.save_map(mapmeta)
local filepath = path .. "map.mts"
if minetest.create_schematic(mapmeta.pos1, mapmeta.pos2, nil, filepath) then
minetest.chat_send_all(minetest.colorize(ctf_map.CHAT_COLOR, "Saved Map '" .. mapmeta.name .. "' to " .. path))
minetest.chat_send_all(minetest.colorize(ctf_map.CHAT_COLOR,
"To play, move it to \""..minetest.get_modpath(modname).."/maps/"..mapmeta.dirname..", "..
"start a normal ctf game, and run \"/ctf_next "..mapmeta.dirname.."\" then \"/ctf_skip\""));
else
minetest.chat_send_all(minetest.colorize(ctf_map.CHAT_COLOR, "Map Saving Failed!"))
end

View File

@ -2,6 +2,30 @@ ctf_gui.old_init()
local context = {}
local function greet_player(player)
minetest.chat_send_player(
player:get_player_name(),
minetest.colorize(ctf_map.CHAT_COLOR, "Welcome! This server is in mapedit mode.\n")
)
if not minetest.check_player_privs(player, "ctf_map_editor") then
minetest.chat_send_player(
player:get_player_name(),
minetest.colorize(ctf_map.CHAT_COLOR,
"To start, grant yourself \"ctf_map_editor\""..
"using \"/grantme ctf_map_editor\" Then run \"/ctf_map editor\"")
)
else
minetest.chat_send_player(
player:get_player_name(),
minetest.colorize(ctf_map.CHAT_COLOR, "To start, run \"/ctf_map editor\"")
)
end
end
if ctf_core.settings.server_mode == "mapedit" then
minetest.register_on_joinplayer(greet_player)
end
local function edit_map(pname, map)
local p = minetest.get_player_by_name(pname)
@ -22,13 +46,23 @@ local function edit_map(pname, map)
minetest.settings:set("time_speed", map.time_speed * 72)
minetest.registered_chatcommands["time"].func(pname, tostring(map.start_time))
minetest.after(8, function()
minetest.fix_light(map.pos1, map.pos2)
end)
minetest.after(8, minetest.fix_light, map.pos1, map.pos2)
context[pname] = map
end
function ctf_map.set_flag_location(pname, teamname, pos)
if context[pname] == nil then
return
end
if context[pname].teams[teamname] == nil then
context[pname].teams[teamname] = {}
end
context[pname].teams[teamname].flag_pos = pos
end
function ctf_map.show_map_editor(player)
if context[player] then
ctf_map.show_map_save_form(player)
@ -51,7 +85,9 @@ function ctf_map.show_map_editor(player)
pos = {"center", 0},
func = function(pname)
minetest.chat_send_player(pname,
"Please decide what the size of your map will be and punch nodes on two opposite corners of it")
minetest.colorize(ctf_map.CHAT_COLOR,
"Please decide what the size of your map will be "..
"and punch nodes on two opposite corners of it"))
ctf_map.get_pos_from_player(pname, 2, function(p, positions)
local pos1, pos2 = vector.sort(positions[1], positions[2])
@ -83,13 +119,27 @@ function ctf_map.show_map_editor(player)
game_modes = {},
}
minetest.chat_send_player(pname, "Build away!")
minetest.chat_send_player(pname, minetest.colorize(ctf_map.CHAT_COLOR,
"Build away! When you are done, run \"/ctf_map editor\""))
end)
end,
},
currentmaps = {
type = "textlist",
pos = {"center", 1.7},
size = {6, 6},
items = dirlist_sorted,
func = function(pname, fields)
local event = minetest.explode_textlist_event(fields.currentmaps)
if event.type ~= "INV" then
selected_map = event.index
end
end,
},
editexisting = {
type = "button", exit = true, label = "Edit Existing Map",
pos = {0.1, 1.8},
type = "button", exit = true, label = "Start Editing",
pos = {0.1, 7.8},
func = function(pname, fields)
minetest.after(0.1, function()
ctf_gui.old_show_formspec(pname, "ctf_map:loading", {
@ -110,8 +160,8 @@ function ctf_map.show_map_editor(player)
end,
},
resume_edit = {
type = "button", exit = true, label = "Resume Editing Map",
pos = {(8-ctf_gui.ELEM_SIZE.x) - 0.3, 1.8},
type = "button", exit = true, label = "Resume Editing",
pos = {(8-ctf_gui.ELEM_SIZE.x) - 0.3, 7.8},
func = function(pname, fields)
minetest.after(0.1, function()
ctf_gui.old_show_formspec(pname, "ctf_map:loading", {
@ -130,19 +180,6 @@ function ctf_map.show_map_editor(player)
end)
end,
},
currentmaps = {
type = "textlist",
pos = {"center", 1.9 + ctf_gui.ELEM_SIZE.y},
size = {6, 6},
items = dirlist_sorted,
func = function(pname, fields)
local event = minetest.explode_textlist_event(fields.currentmaps)
if event.type ~= "INV" then
selected_map = event.index
end
end,
},
}
})
end
@ -377,13 +414,23 @@ function ctf_map.show_map_save_form(player, scroll_pos)
size = {5, ctf_gui.ELEM_SIZE.y},
func = function(pname, fields)
ctf_map.get_pos_from_player(pname, 1, function(name, positions)
local p = positions[1]
local pos = positions[1]
local node = minetest.get_node(pos).name
context[pname].teams[teamname].flag_pos = p
if string.match(node, "^ctf_modebase:flag_top_.*$") then
pos = vector.offset(pos, 0, -1, 0)
elseif node ~= "air" and node ~= "ctf_modebase:flag" then
pos = vector.offset(pos, 0, 1, 0)
end
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, minetest.explode_scrollbar_event(fields.formcontent).value)
end)
ctf_map.set_flag_location(pname, teamname, pos)
local facedir = minetest.dir_to_facedir(minetest.get_player_by_name(pname):get_look_dir())
minetest.set_node(pos, {name="ctf_modebase:flag", param2=facedir})
minetest.set_node(vector.offset(pos, 0, 1, 0), {name="ctf_modebase:flag_top_"..teamname, param2 = facedir})
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end)
end,
}
@ -407,10 +454,13 @@ function ctf_map.show_map_save_form(player, scroll_pos)
" - " .. minetest.pos_to_string(context[player].teams[teamname].pos2, 0),
pos = {0.2, idx-(ctf_gui.ELEM_SIZE.y/2)},
size = {9 - (ctf_gui.SCROLLBAR_WIDTH + 0.1), ctf_gui.ELEM_SIZE.y},
func = function(pname)
func = function(pname, fields)
ctf_map.get_pos_from_player(pname, 2, function(p, positions)
context[pname].teams[teamname].pos1 = positions[1]
context[pname].teams[teamname].pos2 = positions[2]
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end)
end,
}
@ -438,9 +488,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
func = function(pname, fields)
context[pname].teams[teamname].look_pos = nil
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, minetest.explode_scrollbar_event(fields.formcontent).value)
end)
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end,
}
@ -454,9 +503,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
ctf_map.get_pos_from_player(pname, 1, function(_, positions)
context[pname].teams[teamname].look_pos = positions[1]
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, minetest.explode_scrollbar_event(fields.formcontent).value)
end)
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end)
end,
exit = true,
@ -477,9 +525,7 @@ function ctf_map.show_map_save_form(player, scroll_pos)
pos2 = vector.new(),
amount = ctf_map.DEFAULT_CHEST_AMOUNT,
})
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, "max")
end)
minetest.after(0.1, ctf_map.show_map_save_form, pname, "max")
end,
}
idx = idx + 1
@ -500,9 +546,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
context[pname].chests[id].pos1 = new_positions[1]
context[pname].chests[id].pos2 = new_positions[2]
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, minetest.explode_scrollbar_event(fields.formcontent).value)
end)
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end)
end,
}
@ -529,9 +574,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
size = {ctf_gui.ELEM_SIZE.y, ctf_gui.ELEM_SIZE.y},
func = function(pname, fields)
table.remove(context[pname].chests, id)
minetest.after(0.1, function()
ctf_map.show_map_save_form(pname, minetest.explode_scrollbar_event(fields.formcontent).value)
end)
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end,
}
idx = idx + 1
@ -545,10 +589,13 @@ function ctf_map.show_map_save_form(player, scroll_pos)
" - " .. minetest.pos_to_string(context[player].barrier_area.pos2, 0),
pos = {0, idx},
size = {9 - (ctf_gui.SCROLLBAR_WIDTH + 0.1), ctf_gui.ELEM_SIZE.y},
func = function(pname)
func = function(pname, fields)
ctf_map.get_pos_from_player(pname, 2, function(p, positions)
context[pname].barrier_area.pos1 = positions[1]
context[pname].barrier_area.pos2 = positions[2]
minetest.after(0.1, ctf_map.show_map_save_form, pname,
minetest.explode_scrollbar_event(fields.formcontent).value)
end)
end,
}
@ -575,6 +622,20 @@ function ctf_map.show_map_save_form(player, scroll_pos)
end)
end,
}
-- CANCEL EDITING
elements.cancelediting = {
type = "button",
exit = true,
pos = {ctf_gui.FORM_SIZE.x - ctf_gui.SCROLLBAR_WIDTH - ctf_gui.ELEM_SIZE.x - 1, idx},
label = "Cancel Editing",
func = function(pname)
minetest.after(0.1, function()
context[pname] = nil
ctf_map.show_map_editor(player)
end)
end,
}
idx = idx + 1
-- Show formspec

View File

@ -1,2 +1,3 @@
name = ctf_map
depends = ctf_core, ctf_teams, ctf_gui, default, ctf_changes, skybox
optional_depends = worldedit

View File

@ -28,7 +28,7 @@ minetest.register_node("ctf_map:ind_glass", {
walkable = true,
buildable_to = false,
pointable = ctf_core.settings.server_mode == "mapedit",
groups = {immortal = 1, not_in_creative_inventory = 1},
groups = {immortal = 1},
sounds = default.node_sound_glass_defaults()
})
@ -45,14 +45,14 @@ minetest.register_node("ctf_map:ind_glass_red", {
use_texture_alpha = false,
alpha = 0,
pointable = ctf_core.settings.server_mode == "mapedit",
groups = {immortal = 1, not_in_creative_inventory = 1},
groups = {immortal = 1},
sounds = default.node_sound_glass_defaults()
})
ctf_map.barrier_nodes[minetest.get_content_id("ctf_map:ind_glass_red")] = minetest.CONTENT_AIR
minetest.register_node("ctf_map:ind_stone_red", {
description = "Indestructible Red Barrier Stone",
groups = {immortal = 1, not_in_creative_inventory = 1},
groups = {immortal = 1},
tiles = {"ctf_map_stone_red.png"},
is_ground_content = false
})

View File

@ -14,32 +14,6 @@ if ctf_core.settings.server_mode == "play" then
end
end
local function show_flag_color_form(player, target_pos, param2)
ctf_gui.old_show_formspec(player, "ctf_modebase:flag_color_select", {
title = "Flag Color Selection",
description = "Choose a color for this flag",
privs = {ctf_map_editor = true},
elements = {
teams = {
type = "dropdown",
pos = {"center", 0.5},
items = ctf_teams.teamlist,
},
choose = {
type = "button",
label = "Choose",
exit = true,
pos = {"center", 1.5},
func = function(playername, fields)
if not target_pos or not fields.teams then return end
minetest.set_node(target_pos, {name = "ctf_modebase:flag_top_"..fields.teams, param2 = param2})
end,
},
},
})
end
-- The flag
minetest.register_node("ctf_modebase:flag", {
description = "Flag",
@ -62,7 +36,7 @@ minetest.register_node("ctf_modebase:flag", {
{0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500}
}
},
groups = {immortal=1,is_flag=1,flag_bottom=1},
groups = {immortal=1,is_flag=1,flag_bottom=1,not_in_creative_inventory=1},
on_punch = function(pos, node, puncher, pointed_thing, ...)
local pos_above = vector.offset(pos, 0, 1, 0)
local node_above = minetest.get_node(pos_above)
@ -74,11 +48,7 @@ minetest.register_node("ctf_modebase:flag", {
minetest.node_punch(pos, node, puncher, pointed_thing, ...)
end,
on_rightclick = function(pos, node, clicker)
local pos_above = vector.offset(pos, 0, 1, 0)
if ctf_core.settings.server_mode == "mapedit" then
show_flag_color_form(clicker, pos_above, node.param2)
else
if ctf_core.settings.server_mode ~= "mapedit" then
ctf_modebase.on_flag_rightclick(clicker)
end
end,
@ -120,9 +90,7 @@ for name, def in pairs(ctf_teams.team) do
minetest.node_punch(pos, node, puncher, pointed_thing, ...)
end,
on_rightclick = function(pos, node, clicker)
if ctf_core.settings.server_mode == "mapedit" then
show_flag_color_form(clicker, pos, node.param2)
else
if ctf_core.settings.server_mode ~= "mapedit" then
ctf_modebase.on_flag_rightclick(clicker)
end
end,