Level Editor/Scripting/Examples/King of the Hill

Revision as of 04:44, 20 October 2026 by TeamSpecBot (talk | contribs) (American spelling)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

One zone in the middle of the map. A team scores a point for every second it holds the hill with nobody from the other team on it, and the first team to 60 wins.

The map

  • A brush over the hill with Is Area ticked and Trigger Name set to Hill. Leave Zone Team at 0.
  • Spawn points for team 1 and team 2.
  • A World Boundary below the map.

server.gd

extends GamemodeServerScript

const HILL := &"Hill"
const WIN_SCORE := 60

## Seconds held towards the next point.
var _held: float = 0.0


func _declare() -> void:
	api.declare_team_counter(&"score", 0, 0, WIN_SCORE)
	# Who holds the hill right now: 0 for nobody, or when it is contested.
	api.declare_global_counter(&"holder", 0, 0, 2)


func _start() -> void:
	if api.zones_labeled(HILL).is_empty():
		api.log("no zone named Hill in this map")


func _player_died(entity_id: int, _killer_id: int, default_seconds: float) -> void:
	api.respawn_in(entity_id, default_seconds)


func _zone_tick(zone: StringName, occupants: Array, delta: float) -> void:
	if api.zone_label(zone) != HILL:
		return
	var holder: int = 0
	for entity_id in occupants:
		var team: int = api.team_of(entity_id)
		if holder == 0:
			holder = team
		elif team != holder:
			holder = 0
			break
	if holder != api.get_global(&"holder"):
		api.set_global(&"holder", holder)
		if holder != 0:
			api.notify_all("%s took the hill" % api.team_name(holder))
	if holder == 0:
		_held = 0.0
		return
	_held += delta
	while _held >= 1.0:
		_held -= 1.0
		if api.add_team(holder, &"score", 1) >= WIN_SCORE:
			api.declare_winner(holder, "held the hill for %d seconds" % WIN_SCORE)

What it uses:

  • _zone_tick runs for the hill every tick, empty or not, so the timer resets the moment the hill is empty or contested. See Zones and area triggers.
  • The holder counter only changes when control changes, so the message goes out once per change rather than every tick.
  • add_team returns the new score, which is all the win check needs. api.declare_winner ends the round; the game does the rest.

client.gd

extends GamemodeClientScript


func _start() -> void:
	api.show_widget(&"team_score", {
		"counter": "score",
		"threshold": api.counter_maximum(api.SCOPE_TEAM, &"score"),
		"warn_at": api.counter_maximum(api.SCOPE_TEAM, &"score") - 10,
	})

The client script reads the winning score from the counter's declared maximum, so changing WIN_SCORE in the server script is the only edit a different target needs.

bot.gd

extends GamemodeBotScript

const HILL := &"Hill"
const REVIEW_SECONDS := 1.0

var _hill: StringName = &""
var _review: float = 0.0


func _start() -> void:
	var hills: Array = api.zones_labeled(HILL)
	if not hills.is_empty():
		_hill = hills[0]


func _player_joined(entity_id: int) -> void:
	if api.is_bot(entity_id):
		api.bot_set_skill(entity_id, 0.5)


func _tick(delta: float) -> void:
	_review -= delta
	if _review > 0.0 or _hill == &"":
		return
	_review = REVIEW_SECONDS
	var at: Vector3 = api.zone_position(_hill)
	for entity_id in api.bots():
		if api.is_alive(entity_id):
			api.bot_defend(entity_id, at, 8.0)

Every bot is told to hold the hill once a second. Orders do not expire, so giving the same one again is the normal way to keep it; see Bots.

TeamSpec Level Editor
Basics Getting started · Interface · Viewport and camera · Selecting and hiding · Keyboard shortcuts · Settings · Map Config · Troubleshooting
Menus File · Edit · View · Tools · Functional
Panels Scene tree · Inspector · Materials · Props · Snapping · UV editor · Displacement · Terrain
Tools Move, Rotate and Scale · Box · Sphere · Cylinder · Prism · Stairs · Spiral Stairs · Spiral Tower · Arch · Path Brush · Plane · Terrain · Clip · Duplicate · Ruler and Cordon · Edit Vertex and Edge · Edit Face · Loop Cut · Material Brush · Vertex Color Brush · Displacement · Prop Path
Entities Brush · Zone · Group · Mirror Node · Prop · Spawn Point · Supply Pickup · World Boundary · Lights · Water · Sounds · Anchor Platform · Payload Track · Boss Spawn · Map Marker · World Text · Decal · Door · Button · Logic entities · 3D Skybox · Nav Region
Workflows Your first map · Brushes and CSG · Materials and UVs · Lighting and lightmaps · Bots and navigation · Mirrored maps · Outputs and logic · Custom assets · Playtesting · Exporting and publishing · Importing Source maps
Scripting Overview · Server rules · Client HUD · Bots · Teams and spawning · Zones · Pickups · Payload tracks · Map entities · Sandbox limits · Testing · King of the Hill · Team Deathmatch · API reference · What a script may use