Class: Esp::Mw::DialogueDsl
- Inherits:
-
Object
- Object
- Esp::Mw::DialogueDsl
- Defined in:
- lib/esp/mw/dialogue_dsl.rb
Overview
Block-style DSL for emitting Dialogue (DIAL) and DialogueInfo (INFO) records without the bookkeeping. Used inside Ruby mod sources via the loader-exposed ‘dialogue { … }` helper.
The contract: a ‘dialogue` block returns a flat Array of records that the author splats into their mod’s records array. The DSL handles:
-
One DIAL record per ‘topic`, with `dialogue_type` carried through.
-
INFO records under each topic, chained via prev_id/next_id in author order — Morrowind evaluates filters top-down, so author order is filter precedence.
-
Speaker scoping via ‘speaker “Name” do … end`. Nested blocks inherit; an explicit `speaker:` on an info wins.
-
i18n: a ‘t(“key”)` helper is available inside the block when the loader passes an Esp::Mw::I18n instance.
Example:
dialogue do
speaker "Hrisskar Flat-Foot" do
topic "Flat-Foot" do
info t("hrisskar.flat_foot.intro")
info t("hrisskar.flat_foot.legion"), pc_faction: "Imperial Legion"
end
end
topic "AFSN_Tracker", type: :journal do
info "Stage 10 text", journal_index: 10
info "Stage 20 text", journal_index: 20
end
end
Supported info kwargs:
speaker: speaker_id filter (overrides surrounding scope)
race: speaker_race filter
class: speaker_class filter (note the keyword clash —
use the string key or the `class_:` alias)
faction: speaker_faction filter
cell: speaker_cell filter
sex: :any | :female | :male
pc_faction: player_faction filter
pc_rank: player_rank filter
speaker_rank: NPC rank filter
disposition: minimum disposition
sound: sound_path
result_script: inline MWScript text to run on activation
result_script_source: path to .mwscript file (resolved relative to
the mod source dir at preflight time)
journal_index: for Journal-type topics — maps to data.disposition
Constant Summary collapse
- VALID_TYPES =
%i[topic journal persuasion greeting voice].freeze
- VALID_SEX =
%i[any female male].freeze
Instance Attribute Summary collapse
-
#records ⇒ Object
readonly
Returns the value of attribute records.
Class Method Summary collapse
- .build(i18n: nil, &block) ⇒ Object
-
.from_spec(spec) ⇒ Object
Data-driven equivalent of ‘build`, for callers that can’t pass a Ruby block (MCP/HTTP).
Instance Method Summary collapse
- #info(text, **filters) ⇒ Object
-
#initialize(i18n: nil) ⇒ DialogueDsl
constructor
A new instance of DialogueDsl.
- #speaker(name, &block) ⇒ Object
- #topic(name, type: :topic, &block) ⇒ Object
Constructor Details
#initialize(i18n: nil) ⇒ DialogueDsl
Returns a new instance of DialogueDsl.
83 84 85 86 87 88 89 90 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 83 def initialize(i18n: nil) @records = [] @speaker_stack = [] @current_topic = nil return unless i18n define_singleton_method(:t) { |key| i18n.t(key) } end |
Instance Attribute Details
#records ⇒ Object (readonly)
Returns the value of attribute records.
81 82 83 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 81 def records @records end |
Class Method Details
.build(i18n: nil, &block) ⇒ Object
57 58 59 60 61 62 63 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 57 def self.build(i18n: nil, &block) raise ArgumentError, Esp.t('errors.dialogue.needs_block') unless block dsl = new(i18n: i18n) dsl.instance_eval(&block) dsl.records end |
.from_spec(spec) ⇒ Object
Data-driven equivalent of ‘build`, for callers that can’t pass a Ruby block (MCP/HTTP). Spec is JSON-shaped:
{ "topics" => [ { "name"=>, "type"=>, "speaker"=>,
"infos"=>[ { "text"=>, <filter keys>... } ] } ] }
Filter keys mirror the block DSL’s info kwargs. ‘text` carries literal strings or `@t:` sentinels (resolved at build), so there’s no t() helper and no i18n instance here — unlike the block form.
72 73 74 75 76 77 78 79 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 72 def self.from_spec(spec) topics = spec.is_a?(Hash) ? spec['topics'] : spec raise ArgumentError, Esp.t('errors.dialogue.needs_topics') unless topics.is_a?(Array) dsl = new topics.each { |t| dsl.send(:apply_topic_spec, t) } dsl.records end |
Instance Method Details
#info(text, **filters) ⇒ Object
114 115 116 117 118 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 114 def info(text, **filters) raise Esp.t('errors.dialogue.info_outside_topic') unless @current_topic @current_topic[:infos] << { text: text, filters: filters } end |
#speaker(name, &block) ⇒ Object
92 93 94 95 96 97 98 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 92 def speaker(name, &block) raise ArgumentError, Esp.t('errors.dialogue.speaker_needs_block') unless block @speaker_stack.push(name) instance_eval(&block) @speaker_stack.pop end |
#topic(name, type: :topic, &block) ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/esp/mw/dialogue_dsl.rb', line 100 def topic(name, type: :topic, &block) unless VALID_TYPES.include?(type) raise ArgumentError, Esp.t('errors.dialogue.bad_topic_type', types: VALID_TYPES) end raise ArgumentError, Esp.t('errors.dialogue.topic_needs_block') unless block raise Esp.t('errors.dialogue.nested_topics') if @current_topic @records << { 'type' => 'Dialogue', 'flags' => '', 'id' => name, 'dialogue_type' => normalize_type(type) } @current_topic = { name: name, type: type, infos: [] } instance_eval(&block) flush_topic end |