Files
claude c4beeae38f Initial commit: Transcience MVP
Top-down twin-stick bullet-hell, Godot 4.7, server-authoritative dedicated
server with client-side prediction. Clients send input only; the server
resolves every hit for both players and enemies (no PvP).

- SimWorld: whole simulation as plain RefCounted objects (no nodes, no
  physics server), ~0.24ms/tick at peak load -- runs headless for free and
  drives 78 tests in under a second
- BulletPool: struct-of-arrays bullet storage, replicated as spawn/despawn
  events rather than per-tick state
- Emitter framework (Ring/AimedSpread/WallGap/ArcSweep) shared by trash
  enemies and bosses -- a new boss is data in src/content/content.gd, no
  simulation changes
- The Warden of the Fold: stationary 4-phase boss built entirely on that
  format
- Lobby hub with a portal into on-demand dungeon instances; one process
  hosts the hub plus every concurrent dungeon
- Emergency escape: 3s server-owned channel, cancelled by damage
- tools/check.sh, test.sh (GUT), smoke.sh (real server + bot clients over
  ENet), bench.gd; git hooks wired to the same scripts
- docs/ARCHITECTURE.md, NETCODE.md, WORKFLOW.md, ROADMAP.md
2026-09-03 16:03:57 +02:00

95 lines
2.9 KiB
GDScript

# ------------------------------------------------------------------------------
# There was an error that someone found in Godot 4.4.1, but ended up being a
# different error in Godot 4.5. The fix was to hold a reference to the font
# so that TextEdit control did not lose the font when switching. This is
# the solution I came up with. Just hold a reference to all fonts we use,
# but only when we use them. Basically a lazy loader with some semantics for
# font names and location.
#
# https://github.com/bitwes/Gut/issues/749
#
# An instance of this could be used to allow users to specify their own fonts.
# It's not perect for that yet, but it is feasible.
# ------------------------------------------------------------------------------
const DEFAULT_CUSTOM_FONT_NAME = 'CourierPrime'
const THEME_FONT_TO_FONT_TYPES_MAP = {
'font':FONT_TYPES.REGULAR,
'normal_font': FONT_TYPES.REGULAR,
'bold_font': FONT_TYPES.BOLD,
'italics_font':FONT_TYPES.ITALIC,
'bold_italics_font':FONT_TYPES.BOLD_ITALIC
}
# Values for FONT_TYPES are based on Google font file suffix (not extension).
# A font file will be a key from fonts + - + FONT_TYPE value + .ttf.
const FONT_TYPES = {
REGULAR = 'Regular',
BOLD = 'Bold',
ITALIC = 'Italic',
BOLD_ITALIC = 'BoldItalic'
}
var fonts = {
'AnonymousPro':{},
'CourierPrime':{},
'LobsterTwo':{},
'Default':{}
}
var custom_font_path = 'res://addons/gut/fonts/'
func _init():
_populate_default_fonts()
func _populate_default_fonts():
var ctrl = TextEdit.new()
var f = ctrl.get_theme_font('font')
for key in FONT_TYPES:
fonts['Default'][FONT_TYPES[key]] = f
ctrl.free()
func _load_font(font_name, font_type, font_path):
var dynamic_font = FontFile.new()
dynamic_font.load_dynamic_font(font_path)
fonts[font_name][font_type] = dynamic_font
func get_font(font_name, font_type='Regular'):
if(!fonts.has(font_name)):
push_error(str("Invalid font name '", font_name, "'"))
return fonts['Default'][FONT_TYPES.REGULAR]
if(!FONT_TYPES.values().has(font_type)):
push_error(str("Invalid font type '", font_type, "'"))
return fonts['Default'][FONT_TYPES.REGULAR]
if(!fonts[font_name].has(font_type)):
var filename = custom_font_path.path_join(str(font_name, '-', font_type, '.ttf'))
if(FileAccess.file_exists(filename)):
_load_font(font_name, font_type, filename)
else:
push_error(str("Missing custom font ", filename))
return fonts['Default'][FONT_TYPES.REGULAR]
return fonts.get(font_name, {}).get(font_type, null)
func get_font_names():
return fonts.keys()
# Maps the various theme font names (font, normal_font, italics_font etc) to
# a FONT_TYPE.
func get_font_for_theme_font_name(theme_font_name, custom_font_name):
if(!THEME_FONT_TO_FONT_TYPES_MAP.has(theme_font_name)):
push_error(str("Unknown theme font name ", theme_font_name))
return get_font(custom_font_name)
return get_font(custom_font_name, THEME_FONT_TO_FONT_TYPES_MAP[theme_font_name])