Rework battle effects to use Razor scripts for control

Implement Fire1 as a first effect test, woo
This commit is contained in:
ficedula 2023-10-02 23:31:53 +01:00
parent 50e725191f
commit efc8eb0ac3
23 changed files with 729 additions and 326 deletions

View File

@ -126,6 +126,7 @@ namespace Braver {
SaveMap = new SaveMap(Memory);
}
public int GameTimeFrames => GameTimeSeconds * 30 + SaveMap.GameTimeFrames;
public int GameTimeSeconds {
get => SaveMap.GameTimeSeconds + 60 * SaveMap.GameTimeMinutes + 60 * 60 * SaveMap.GameTimeHours;
set {

View File

@ -31,6 +31,7 @@ namespace Braver.Battle {
public CombatStats BaseStats { get; }
public string Name { get; }
public int ID { get; set; }
public int HP { get; set; }
public int MaxHP { get; }
@ -109,6 +110,7 @@ namespace Braver.Battle {
private CombatStats _stats;
public string Name => _char.Name;
public int ID { get; set; }
public Character Character => _char;
public List<CharacterAction> Actions { get; } = new();
@ -263,6 +265,7 @@ namespace Braver.Battle {
private CombatStats _stats;
public string Name { get; private set; }
public int ID { get; set; }
public EnemyInstance Enemy => _enemy;
public AI AI { get; set; }

View File

@ -27,12 +27,6 @@ namespace Braver.Battle {
Counter,
}
public interface IInProgress {
string Description { get; }
bool Step(int frames); //return true if done
bool IsIndefinite { get; }
}
public class QueuedAction {
public ICombatant Source { get; private set; }
public Ability Ability { get; private set; }
@ -104,6 +98,7 @@ namespace Braver.Battle {
foreach(var comb in ActiveCombatants) {
comb.ID = _combatants.IndexOf(comb);
comb.VTimer = new Timer(_speedValue * 2, 8192, 0);
comb.CTimer = new Timer(136, 8192, 0);

View File

@ -37,18 +37,27 @@ namespace Braver {
void SetMusicVolume(byte? volumeFrom, byte volumeTo, float duration);
void PlayMusic(string name, bool pushContext = false);
void StopMusic(bool popContext = false);
void Precache(Sfx which, bool pin);
void Precache(int which, bool pin);
void ChannelProperty(int channel, float? pan, float? volume);
void GetChannelProperty(int channel, out float? pan, out float? volume);
void StopChannel(int channel);
void StopLoopingSfx(bool includeChannels);
void PlaySfx(int which, float volume, float pan, int? channel = null);
void PlaySfx(Sfx which, float volume, float pan, int? channel = null);
IAudioItem LoadStream(string path, string file);
IAudioItem TryLoadStream(string path, string file);
void DecodeStream(Stream s, out byte[] rawData, out int channels, out int frequency);
}
public static class AudioExtensions {
public static void PlaySfx(this IAudio audio, Sfx which, float volume, float pan, int? channel = null) {
audio.PlaySfx((int)which, volume, pan, channel);
}
public static void Precache(this IAudio audio, Sfx which, bool pin) {
audio.Precache((int)which, pin);
}
}
}

View File

@ -75,6 +75,6 @@ namespace Braver.Plugins.UI {
void BattleCharacterReady(ICombatant character);
void BattleTargetHighlighted(IEnumerable<ICombatant> targets);
void BattleActionStarted(string action);
void BattleActionResult(IInProgress result);
//void BattleActionResult(IInProgress result);
}
}

View File

@ -295,12 +295,14 @@ namespace Braver {
};
}
public void Precache(Sfx which, bool pin) {
var effect = GetEffect((int)which);
_sfx[(int)which] = new WeakReference<LoadedEffect>(effect);
public void Precache(int which, bool pin) {
var effect = GetEffect(which);
_sfx[which] = new WeakReference<LoadedEffect>(effect);
if (pin)
_pinned.Add(effect);
else
_recent0.Add(effect);
}
public void StopChannel(int channel) {
@ -339,7 +341,6 @@ namespace Braver {
}
public void PlaySfx(Sfx which, float volume, float pan, int? channel = null) => PlaySfx((int)which, volume, pan, channel);
public void PlaySfx(int which, float volume, float pan, int? channel = null) {
LoadedEffect effect;

View File

@ -13,101 +13,64 @@ using System.Linq;
namespace Braver.Battle {
public class ActionInProgress {
private Dictionary<int?, List<IInProgress>> _items = new();
private string _name;
private Dictionary<IInProgress, int> _frames = new();
private PluginInstances<IBattleUI> _plugins;
public bool IsComplete => !_items.Keys.Any(phase => phase != null);
public ActionInProgress(string name, PluginInstances<IBattleUI> plugins) {
_name = name;
_plugins = plugins;
System.Diagnostics.Trace.WriteLine($"Starting new action {name}");
_plugins.Call(ui => ui.BattleActionStarted(name));
}
public void Add(int? phase, IInProgress inProgress) {
if (!_items.TryGetValue(phase, out var list))
_items[phase] = list = new List<IInProgress>();
list.Add(inProgress);
}
public void Step() {
if (IsComplete) return;
int phase = _items
.Keys
.Where(phase => phase != null)
.Select(phase => phase.Value)
.Min();
var lists = _items
.Where(kv => (kv.Key == null) || (kv.Key == phase))
.Select(kv => kv.Value);
foreach(var list in lists) {
for (int i = list.Count - 1; i >= 0; i--) {
if (!_frames.TryGetValue(list[i], out int frame)) {
frame = 0;
_plugins.Call(ui => ui.BattleActionResult(list[i]));
}
if (list[i].Step(frame) && !list[i].IsIndefinite)
list.RemoveAt(i);
else
_frames[list[i]] = frame + 1;
}
}
var toRemove = _items
.Where(kv => !kv.Value.Any())
.Select(kv => kv.Key)
.ToArray();
foreach(var key in toRemove)
_items.Remove(key);
}
public override string ToString() => _name;
public interface IInProgress {
string Description { get; }
void FrameStep();
void Cancel();
bool IsComplete { get; }
}
public class BattleTitle : IInProgress {
public abstract class TimedInProgress : IInProgress {
protected int _frames;
protected int _frame;
protected string _description;
public string Description => _description;
public bool IsComplete => _frame > _frames;
protected float Progress => 1f * _frame / _frames;
protected abstract void DoStep();
public void FrameStep() {
if (_frame <= _frames)
DoStep();
_frame++;
}
public virtual void Cancel() {
_frame = _frames;
}
}
public class BattleTitle : TimedInProgress {
private string _title;
private int? _frames;
private UI.UIBatch _ui;
private float _alpha;
private bool _announce;
public bool IsIndefinite => _frames == null;
public string Description => _announce ? _title : null;
public BattleTitle(string title, int? frames, UI.UIBatch ui, float alpha, bool announce) {
public BattleTitle(string title, int frames, UI.UIBatch ui, float alpha, bool announce) {
_title = title;
_frames = frames;
_ui = ui;
_alpha = alpha;
_announce = announce;
_announce = announce;
_description = _announce ? _title : null;
}
public bool Step(int frame) {
protected override void DoStep() {
_ui.DrawBox(new Rectangle(0, 0, 1280, 55), 0.97f, _alpha);
_ui.DrawText("main", _title, 640, 15, 0.98f, Color.White, UI.Alignment.Center);
return frame >= _frames.GetValueOrDefault();
}
}
public class BattleResultText : IInProgress {
private int _frames;
public class BattleResultText : TimedInProgress {
private UI.UIBatch _ui;
private Color _color;
private Func<Vector2> _start;
private Vector2 _movement;
private string _text;
public bool IsIndefinite => false;
public string Description { get; private set; }
public BattleResultText(UI.UIBatch ui, string text, Color color, Func<Vector2> start, Vector2 movement, int frames, string description) {
_ui = ui;
@ -116,38 +79,34 @@ namespace Braver.Battle {
_start = start;
_movement = movement;
_frames = frames;
Description = description;
_description = description;
}
public bool Step(int frame) {
var pos = _start() + _movement * frame;
_ui.DrawText("batm", _text, (int)pos.X, (int)pos.Y, 0.96f, _color, UI.Alignment.Center);
return frame++ >= _frames;
protected override void DoStep() {
var pos = _start() + _movement * _frame;
_ui.DrawText("batm", _text, (int)pos.X, (int)pos.Y, 0.96f, _color, UI.Alignment.Center);
}
}
public class EnemyDeath : IInProgress {
private int _frames;
public class EnemyDeath : TimedInProgress {
private Model _model;
public bool IsIndefinite => false;
public string Description { get; private set; }
public EnemyDeath(int frames, ICombatant combatant, Model model) {
_frames = frames;
_model = model;
Description = combatant.Name + " died";
_description = combatant.Name + " died";
}
public bool Step(int frame) {
if (frame < _frames) {
_model.DeathFade = 0.33f - (0.33f * frame / _frames);
} else {
_model.DeathFade = null;
_model.Visible = false;
}
return frame >= _frames;
protected override void DoStep() {
_model.DeathFade = 0.33f - (0.33f * _frame / _frames);
}
public override void Cancel() {
base.Cancel();
_model.DeathFade = null;
_model.Visible = false;
}
}
}

View File

@ -16,10 +16,13 @@ using System.Threading.Tasks;
namespace Braver.Battle {
public class AnimScriptExecutor {
public enum WaitingForKind {
Animation,
Action,
}
private ICombatant _source;
private ICombatant[] _targets;
private RealBattleScreen _screen;
private Engine _engine;
private AnimationScriptDecoder _script;
private Func<bool> _shouldContinue = null;
@ -27,16 +30,27 @@ namespace Braver.Battle {
private List<Action> _renderers = new();
public WaitingForKind? WaitingFor { get; private set; }
public bool IsComplete => !_paused && _complete;
public AnimScriptExecutor(ICombatant source, ICombatant[] targets, RealBattleScreen screen, Engine engine, AnimationScriptDecoder script) {
public AnimScriptExecutor(ICombatant source, RealBattleScreen screen, AnimationScriptDecoder script) {
_source = source;
_targets = targets;
_screen = screen;
_engine = engine;
_script = script;
}
public void Resume() {
switch (WaitingFor) {
case WaitingForKind.Action:
_paused = false;
_shouldContinue = null;
WaitingFor = null;
break;
default:
throw new NotImplementedException();
}
}
public void Step() {
if (_paused) {
if (_shouldContinue()) {
@ -49,11 +63,12 @@ namespace Braver.Battle {
if (op == null)
_complete = true;
else {
var model = _screen.Models[_source];
var model = _screen.Renderer.Models[_source];
if ((byte)op.Value.Op < 0x8E) {
model.PlayAnimation((byte)op.Value.Op, false, 1f, onlyIfDifferent: false);
_paused = true;
_shouldContinue = () => model.AnimationState.CompletionCount > 0;
WaitingFor = WaitingForKind.Animation;
} else {
switch(op.Value.Op) {
@ -66,7 +81,7 @@ namespace Braver.Battle {
Action effRender = null;
var pos = model.Translation + model.Translation2;
effRender = () => {
if (effect.Render(pos, _screen.View3D, frame++))
if (effect.Render(pos, _screen.Renderer.View3D, frame++))
_renderers.Remove(effRender);
};
_renderers.Add(effRender);
@ -82,10 +97,9 @@ namespace Braver.Battle {
break;
case AnimScriptOp.WaitForEffectLoad:
//TODO!!!
_paused = true;
int timeout = 120;
_shouldContinue = () => --timeout == 0;
_shouldContinue = () => false;
WaitingFor = WaitingForKind.Action;
break;
default:

View File

@ -8,6 +8,7 @@ using Braver.UI;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using NAudio.CoreAudioApi.Interfaces;
using NAudio.MediaFoundation;
using System;
using System.Collections.Generic;
using System.Linq;
@ -23,6 +24,7 @@ namespace Braver.Battle {
private RealBattleScreen _screen;
private AnimScriptExecutor _exec;
private SpriteRenderer _sprites;
private BattleEffectManager _effect;
private FGame _game;
private GraphicsDevice _graphics;
@ -55,6 +57,11 @@ namespace Braver.Battle {
}
_sprites.FrameStep();
if (_effect != null) {
_effect.Step();
if (_effect.IsComplete)
_effect = null;
}
}
public void ProcessInput(InputState input) {
@ -75,25 +82,40 @@ namespace Braver.Battle {
if (input.IsJustDown(InputKey.Cancel)) {
_exec = new AnimScriptExecutor(
_engine.ActiveCombatants.ElementAt(_cMenu),
_engine.ActiveCombatants.OfType<EnemyCombatant>().ToArray(),
_screen, _engine,
_screen,
new Ficedula.FF7.Battle.AnimationScriptDecoder(new byte[] { (byte)_anim, 0 })
);
}
if (input.IsJustDown(InputKey.OK)) {
var source = _engine.ActiveCombatants.ElementAt(_cMenu);
var model = _screen.Models[source];
var model = _screen.Renderer.Models[source];
_exec = new AnimScriptExecutor(
source,
_engine.ActiveCombatants.OfType<EnemyCombatant>().ToArray(),
_screen, _engine,
_screen,
new Ficedula.FF7.Battle.AnimationScriptDecoder(model.AnimationScript.Scripts[_script])
);
}
if (input.IsJustDown(InputKey.Start)) {
/*
var sprite = new LoadedSprite(_game, _graphics, "fi_a01.s", new[] { "fire00.tex", "fire01.tex" });
_sprites.Add(sprite, () => _screen.GetModelScreenPos(_engine.ActiveCombatants.ElementAt(_cMenu)));
*/
var source = _engine.ActiveCombatants.ElementAt(_cMenu);
var action = new QueuedAction(
source,
(source as CharacterCombatant).Actions.Select(a => a.Ability).First(a => a.HasValue).Value,
_engine.ActiveCombatants.Where(c => !c.IsPlayer).ToArray(),
ActionPriority.Normal,
"Fire1"
);
_engine.QueueAction(action);
if (_engine.ExecuteNextAction(out var nextAction, out var results)) {
_effect = new BattleEffectManager(
_game, _screen,
nextAction, results, "magic/fire1"
);
};
}
}
@ -102,6 +124,7 @@ namespace Braver.Battle {
_ui.Render();
_exec?.Render();
_sprites.Render();
_effect?.Render();
}
}
}

View File

@ -1,31 +0,0 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Braver.Battle {
public enum EffectCommand {
}
internal class BattleEffect {
}
}
/*
* PRELOAD SPRITE <alias> <spritefile> <tex> <tex> <tex>...
* PRELOAD SUMMON <alias> <summoncode>
* PRELOAD SFX <numberOrName>
*
* SPRITE <combatantID> <xyzOffset>
* SFX <numberOrName>
* CAMERA ...
*
*/

View File

@ -0,0 +1,344 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
using Braver.UI;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Braver.Battle {
public enum EffectCommand {
PreloadSprite,
PreloadSummon,
PreloadSfx,
Sprite,
Sfx,
Camera,
Anim,
Resume,
Wait,
Pause,
Result,
DisplayText,
DeathFade,
ApplyResults,
}
public interface IWaitableEffect {
bool IsComplete { get; }
}
public struct ImmediatelyCompleteEffect : IWaitableEffect {
public bool IsComplete => true;
}
public struct CallbackWaitEffect : IWaitableEffect {
public Func<bool> CheckComplete { get; set; }
public bool IsComplete => CheckComplete();
}
public class EffectTemplate : IBraverTemplateModel {
private AbilityResult[] _results;
private FGame _game;
private QueuedAction _action;
public IEnumerable<AbilityResult> Results => _results;
public QueuedAction Action => _action;
FGame IBraverTemplateModel.Game => _game;
string IBraverTemplateModel.SourceCategory => "battle";
string IBraverTemplateModel.SourceExtension => ".effect";
public EffectTemplate(AbilityResult[] results, FGame game, QueuedAction action) {
_results = results;
_game = game;
_action = action;
}
public string Expand(byte[] text) {
var chars = _game.SaveData.Characters.Select(c => c?.Name).ToArray();
var party = _game.SaveData.Party.Select(c => c?.Name).ToArray();
return Ficedula.FF7.Text.Expand(Ficedula.FF7.Text.Convert(text, 0), chars, party);
}
}
public class BattleEffectManager {
private static Dictionary<EffectCommand, Func<BattleEffectManager, IEnumerable<string>, IWaitableEffect>> _executor = new();
private Dictionary<string, List<IWaitableEffect>> _waitable = new(StringComparer.InvariantCultureIgnoreCase);
private List<IInProgress> _ongoing = new();
private static IWaitableEffect _immediatelyComplete = new ImmediatelyCompleteEffect();
static BattleEffectManager() {
_executor[EffectCommand.Sfx] = (effect, parms) => effect.CmdSfx(parms);
_executor[EffectCommand.PreloadSfx] = (effect, parms) => effect.CmdPreloadSfx(parms);
_executor[EffectCommand.Wait] = (effect, parms) => effect.CmdWait(parms);
_executor[EffectCommand.Pause] = (effect, parms) => effect.CmdPause(parms);
_executor[EffectCommand.PreloadSprite] = (effect, parms) => effect.CmdPreloadSprite(parms);
_executor[EffectCommand.Sprite] = (effect, parms) => effect.CmdSprite(parms);
_executor[EffectCommand.Anim] = (effect, parms) => effect.CmdAnim(parms);
_executor[EffectCommand.Resume] = (effect, parms) => effect.CmdResume(parms);
_executor[EffectCommand.Result] = (effect, parms) => effect.CmdResult(parms);
_executor[EffectCommand.DeathFade] = (effect, parms) => effect.CmdDeathFade(parms);
_executor[EffectCommand.ApplyResults] = (effect, parms) => effect.CmdApplyResults(parms);
_executor[EffectCommand.DisplayText] = (effect, parms) => effect.CmdDisplayText(parms);
}
private IWaitableEffect WaitableFromInProgress(IEnumerable<IInProgress> effects) {
_ongoing.AddRange(effects);
return new CallbackWaitEffect {
CheckComplete = () => effects.All(p => p.IsComplete)
};
}
private IWaitableEffect WaitableFromInProgress(params IInProgress[] effects) {
return WaitableFromInProgress(effects.AsEnumerable());
}
private IWaitableEffect CmdResult(IEnumerable<string> parms) {
var result = _results.Single(r => r.Target.ID.ToString() == parms.ElementAt(0));
IInProgress effect = null;
if (result.Hit) {
if (result.HPDamage != 0) {
effect = new BattleResultText(
_screen.MenuUI, Math.Abs(result.HPDamage).ToString(), result.HPDamage < 0 ? Color.White : Color.Green,
() => _screen.GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} {Math.Abs(result.HPDamage)} {(result.HPDamage >= 0 ? "healing" : "damage")}"
);
}
if (result.MPDamage != 0) {
effect = new BattleResultText(
_screen.MenuUI, $"{Math.Abs(result.MPDamage)} {Font.BATTLE_MP}", result.MPDamage < 0 ? Color.White : Color.Green,
() => _screen.GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} {Math.Abs(result.HPDamage)} MP {(result.HPDamage >= 0 ? "healing" : "damage")}"
);
}
//TODO anything else?!?!
} else {
effect = new BattleResultText(
_screen.MenuUI, Font.BATTLE_MISS.ToString(), Color.White,
() => _screen.GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} missed"
);
}
if (effect != null)
return WaitableFromInProgress(effect);
else
return _immediatelyComplete;
}
private IWaitableEffect CmdDeathFade(IEnumerable<string> parms) {
var who = _models[parms.ElementAt(0)];
return WaitableFromInProgress(new EnemyDeath(60, who, _screen.Renderer.Models[who]));
}
private IWaitableEffect CmdDisplayText(IEnumerable<string> parms) {
string text = string.Join(" ", parms);
return WaitableFromInProgress(new BattleTitle(text, 60, _screen.MenuUI, 1f, true));
}
private IWaitableEffect CmdApplyResults(IEnumerable<string> parms) {
foreach (var result in _results) {
result.Apply(_action);
_screen.UpdateVisualState(result.Target);
}
return _immediatelyComplete;
}
private IWaitableEffect CmdResume(IEnumerable<string> parms) {
var who = _models[parms.ElementAt(0)];
var exec = _anims[who];
switch (exec.WaitingFor) {
case AnimScriptExecutor.WaitingForKind.Action:
exec.Resume();
return _immediatelyComplete;
default:
throw new NotImplementedException();
}
}
private IWaitableEffect CmdAnim(IEnumerable<string> parms) {
var who = _models[parms.ElementAt(0)];
var model = _screen.Renderer.Models[who];
int anim = int.Parse(parms.ElementAt(1));
var exec = _anims[who] = new AnimScriptExecutor(
who, _screen,
new Ficedula.FF7.Battle.AnimationScriptDecoder(model.AnimationScript.Scripts[anim])
);
return new CallbackWaitEffect {
CheckComplete = () => (_anims[who] != exec) || exec.IsComplete || (exec.WaitingFor == AnimScriptExecutor.WaitingForKind.Action)
};
}
private IWaitableEffect CmdSprite(IEnumerable<string> parms) {
var sprite = _spritesByAlias[parms.First()];
var target = _models[parms.ElementAt(1)];
var model = _screen.Renderer.Models[target];
var offset = GraphicsUtil.Parse3(parms.ElementAtOrDefault(2) ?? "0/0/0");
var instance = _screen.Renderer.Sprites.Add(sprite, () => _screen.GetModelScreenPos(target, offset));
return new CallbackWaitEffect {
CheckComplete = () => !instance.IsActive
};
}
private IWaitableEffect CmdPreloadSprite(IEnumerable<string> parms) {
//TODO actually background load!
var sprite = _screen.Sprites.Get(parms.ElementAt(1), parms.Skip(2));
_spritesByAlias[parms.ElementAt(0)] = sprite;
return _immediatelyComplete;
}
private IWaitableEffect CmdPause(IEnumerable<string> parms) {
int completeFrames = _game.GameTimeFrames + int.Parse(parms.First());
return new CallbackWaitEffect {
CheckComplete = () => _game.GameTimeFrames >= completeFrames
};
}
private IWaitableEffect CmdSfx(IEnumerable<string> parms) {
int id = int.Parse(parms.First());
//TODO - 3d position
_game.Audio.PlaySfx(id, 1f, 0f);
return _immediatelyComplete;
//TODO - wait for sfx completion?
}
private IWaitableEffect CmdPreloadSfx(IEnumerable<string> parms) {
int id = int.Parse(parms.First());
_game.Audio.Precache(id, false);
return _immediatelyComplete;
//TODO - wait for completion?
}
private IWaitableEffect CmdWait(IEnumerable<string> parms) {
if (parms.First().Equals("All", StringComparison.InvariantCultureIgnoreCase)) {
var allWaiters = _waitable.Values.SelectMany(L => L);
if (allWaiters.Any(w => !w.IsComplete))
return null;
_waitable.Clear();
return _immediatelyComplete;
} else {
var waiters = parms
.Select(id => _waitable.GetValueOrDefault(id))
.Where(L => L != null)
.SelectMany(L => L);
if (waiters.Any(w => !w.IsComplete))
return null;
return _immediatelyComplete;
}
}
private Dictionary<string, ICombatant> _models = new(StringComparer.InvariantCultureIgnoreCase);
private Dictionary<ICombatant, AnimScriptExecutor> _anims = new();
private List<string> _effect;
private int _ip;
private FGame _game;
private Dictionary<string, LoadedSprite> _spritesByAlias = new(StringComparer.InvariantCultureIgnoreCase);
private RealBattleScreen _screen;
private QueuedAction _action;
private AbilityResult[] _results;
public bool IsComplete => _ip >= _effect.Count;
public BattleEffectManager(FGame g, RealBattleScreen screen,
QueuedAction action, AbilityResult[] results, string effect) {
_game = g;
_screen = screen;
_action = action;
_results = results;
var cache = g.Singleton(() => new RazorLayoutCache(g));
var model = new EffectTemplate(results, g, action);
_effect = cache.Compile("battle", effect + ".effect", false)
.Run(template => template.Model = model)
.Split('\r', '\n')
.ToList();
_models["source"] = action.Source;
foreach (var result in results)
_models[result.Target.ID.ToString()] = result.Target;
}
public void Render() {
foreach (var anim in _anims.Values.Where(a => a != null))
anim.Render();
}
public void Step() {
foreach (var anim in _anims.Values.Where(a => a != null))
anim.Step();
foreach (var effect in _ongoing)
effect.FrameStep();
while(_ip < _effect.Count) {
string cmd = _effect[_ip].Trim();
if (string.IsNullOrWhiteSpace(cmd) || cmd.StartsWith('#')) {
_ip++;
continue;
}
var parts = cmd.Split(null)
.Where(s => !string.IsNullOrWhiteSpace(s))
.ToArray();
int index = 0;
string waitID;
if (parts[index].StartsWith('!'))
waitID = parts[index++];
else
waitID = null;
var effectCmd = Enum.Parse<EffectCommand>(parts[index++]);
var result = _executor[effectCmd](this, parts.Skip(index));
if (result != null) {
_ip++;
if (waitID != null) {
if (!_waitable.TryGetValue(waitID, out var list))
_waitable[waitID] = list = new List<IWaitableEffect>();
list.Add(result);
}
} else
return;
}
if (IsComplete) {
foreach (var effect in _ongoing.Where(p => !p.IsComplete))
effect.Cancel();
}
}
}
}
/*
* Prefix any command with !waitID to make it waitable later
*
* PRELOADSPRITE <alias> <spritefile> <tex> <tex> <tex>...
* PRELOADSUMMON <alias> <summoncode>
* PRELOADSFX <numberOrName>
*
* SPRITE <alias> <combatantID> <xyzOffset>
* SFX <numberOrName> <positionAtCombatantID>
* CAMERA ...
* ANIM <combatantID> <animNumber>
* RESUME <combatantID>
* WAIT <id> <id> <id>...
* WAIT ALL
* PAUSE <duration>
* RESULT <combatantID>
*
*/

View File

@ -33,12 +33,14 @@ namespace Braver.Battle {
public FGame Game { get; private set; }
public GraphicsDevice Graphics { get; private set; }
public Dictionary<T, Model> Models { get; } = new();
public SpriteRenderer Sprites { get; }
public PerspView3D View3D => _view;
public BattleRenderer(FGame game, GraphicsDevice graphics, Screen uiScreen) {
Game = game;
Graphics = graphics;
_ui = uiScreen;
Sprites = new SpriteRenderer(graphics);
}
public void SetCamera(BattleCamera cam) {
@ -125,6 +127,7 @@ namespace Braver.Battle {
foreach (var model in Models.Values) {
model.FrameStep();
}
Sprites.FrameStep();
_ui.Step(elapsed);
}
@ -151,6 +154,8 @@ namespace Braver.Battle {
if (model.Visible)
model.Render(_view);
Sprites.Render();
_ui.Render();
}

View File

@ -119,15 +119,11 @@ namespace Braver.Battle {
private bool _toggleMultiSingleTarget;
private ActionInProgress _actionInProgress;
private Action _actionComplete;
private BattleEffectManager _effect;
private SpriteRenderer _sprites;
private Engine _engine;
private BattleDebug _debug;
private PluginInstances<IBattleUI> _plugins;
private BattleRenderer<ICombatant> _renderer;
private UIHandler _uiHandler;
private bool _debugCamera = false;
@ -143,8 +139,9 @@ namespace Braver.Battle {
_plugins.Call(ui => ui.BattleTargetHighlighted(current));
}
public IReadOnlyDictionary<ICombatant, Model> Models => _renderer.Models;
public PerspView3D View3D => _renderer.View3D;
public BattleRenderer<ICombatant> Renderer { get; private set; }
public SpriteManager Sprites { get; private set; }
public UI.UIBatch MenuUI => _menuUI;
public override string Description {
get {
@ -167,7 +164,7 @@ namespace Braver.Battle {
if (position.Z < 0)
model.Rotation = new Vector3(0, 180, 0);
//Defaults to animation 0 so no PlayAnimation required
_renderer.Models.Add(combatant, model);
Renderer.Models.Add(combatant, model);
Game.Net.Send(new AddBattleModelMessage {
Code = code,
Position = position,
@ -176,7 +173,7 @@ namespace Braver.Battle {
}
public void UpdateVisualState(ICombatant combatant) {
var model = _renderer.Models[combatant];
var model = Renderer.Models[combatant];
switch(combatant) {
case CharacterCombatant chr:
if (chr.HP <= 0)
@ -261,13 +258,11 @@ namespace Braver.Battle {
var ui = new UI.Layout.LayoutScreen("battle", _uiHandler, isEmbedded: true);
ui.Init(Game, Graphics);
_renderer = new BattleRenderer<ICombatant>(g, graphics, ui);
_renderer.LoadBackground(_scene.LocationID);
Renderer = new BattleRenderer<ICombatant>(g, graphics, ui);
Renderer.LoadBackground(_scene.LocationID);
var cam = _scene.Cameras[0];
_renderer.SetCamera(cam);
_sprites = new SpriteRenderer(graphics);
Renderer.SetCamera(cam);
foreach (var enemy in _scene.Enemies) {
AddModel(
SceneDecoder.ModelIDToFileName(enemy.Enemy.ID),
@ -286,6 +281,7 @@ namespace Braver.Battle {
_menuUI = new UI.UIBatch(Graphics, Game);
_plugins = GetPlugins<IBattleUI>(_formationID.ToString());
Sprites = new SpriteManager(g, graphics);
g.Net.Send(new Net.ScreenReadyMessage());
g.Audio.PlayMusic("bat", true); //TODO!
@ -391,10 +387,12 @@ namespace Braver.Battle {
}
}
public Vector3 GetModelScreenPos(ICombatant combatant) {
var model = _renderer.Models[combatant];
public Vector3 GetModelScreenPos(ICombatant combatant, Vector3? offset = null) {
var model = Renderer.Models[combatant];
var middle = (model.MaxBounds + model.MinBounds) * 0.5f;
var screenPos = _renderer.View3D.ProjectTo2D(model.Translation + middle);
if (offset != null)
middle += offset.Value;
var screenPos = Renderer.View3D.ProjectTo2D(model.Translation + middle);
return screenPos;
}
@ -437,8 +435,6 @@ namespace Braver.Battle {
_uiHandler.Update(_activeMenu?.Combatant);
_menuUI.Reset();
_activeMenu?.Step();
var action = _activeMenu?.SelectedAction;
@ -465,13 +461,11 @@ namespace Braver.Battle {
}
}
if (_actionInProgress != null) {
_actionInProgress.Step();
if (_actionInProgress.IsComplete) {
System.Diagnostics.Trace.WriteLine($"Action {_actionInProgress} now complete");
_actionInProgress = null;
_actionComplete?.Invoke();
_actionComplete = null;
if (_effect != null) {
_effect.Step();
if (_effect.IsComplete) {
System.Diagnostics.Trace.WriteLine($"Action {_effect} now complete");
_effect = null;
}
} else {
/*
@ -481,18 +475,18 @@ namespace Braver.Battle {
* -Or, Check for battle end, and if not, execute any other queued action
*/
var pendingDeadEnemies = _renderer.Models
var pendingDeadEnemies = Renderer.Models
.Where(kv => !kv.Key.IsPlayer && !kv.Key.IsAlive())
.Where(kv => kv.Value.Visible);
if (_engine.AnyQueuedCounters)
DoExecuteNextQueuedAction();
else if (pendingDeadEnemies.Any()) {
_actionInProgress = new ActionInProgress("FadeDeadEnemies", _plugins);
foreach (var enemy in pendingDeadEnemies) {
_actionInProgress.Add(1, new EnemyDeath(60, enemy.Key, enemy.Value));
Game.Audio.PlaySfx(Sfx.EnemyDeath, 1f, 0f); //TODO 3d position?!
}
_effect = new BattleEffectManager(
Game, this, null,
pendingDeadEnemies.Select(e => new AbilityResult { Target = e.Key }).ToArray(),
"FadeDeadEnemies"
);
} else if (CheckForBattleEnd()) {
//
} else
@ -501,78 +495,24 @@ namespace Braver.Battle {
}
protected override void DoStep(GameTime elapsed) {
_menuUI.Reset();
if (_debug != null) {
_debug.Step();
} else {
EngineTick(elapsed);
}
_renderer.Step(elapsed);
Renderer.Step(elapsed);
}
private void DoExecuteNextQueuedAction() {
if (_engine.ExecuteNextAction(out var nextAction, out var results)) {
_actionInProgress = new ActionInProgress($"Action {nextAction.Name} by {nextAction.Source.Name}", _plugins);
_actionComplete += () => {
foreach (var result in results) {
result.Apply(nextAction);
UpdateVisualState(result.Target);
}
};
int phase = 0;
if (nextAction.QueuedText.Any()) {
var chars = Game.SaveData.Characters.Select(c => c?.Name).ToArray();
var party = Game.SaveData.Party.Select(c => c?.Name).ToArray();
foreach (var text in nextAction.QueuedText) {
_actionInProgress.Add(phase++, new BattleTitle(
Ficedula.FF7.Text.Expand(Ficedula.FF7.Text.Convert(text, 0), chars, party),
60, _menuUI, 1f, true
));
}
}
//TODO this isn't always needed
_actionInProgress.Add(phase, new BattleTitle(
nextAction.Name ?? "(Unknown)", 60, _menuUI, 0.75f, false
));
//TODO all the animations!!
foreach (var result in results) {
if (result.Hit) {
if (result.HPDamage != 0) {
_actionInProgress.Add(phase, new BattleResultText(
_menuUI, Math.Abs(result.HPDamage).ToString(), result.HPDamage < 0 ? Color.White : Color.Green,
() => GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} {Math.Abs(result.HPDamage)} {(result.HPDamage >= 0 ? "healing" : "damage")}"
));
}
if (result.MPDamage != 0) {
_actionInProgress.Add(phase, new BattleResultText(
_menuUI, $"{Math.Abs(result.MPDamage)} {Font.BATTLE_MP}", result.MPDamage < 0 ? Color.White : Color.Green,
() => GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} {Math.Abs(result.HPDamage)} MP {(result.HPDamage >= 0 ? "healing" : "damage")}"
));
}
//TODO anything else?!?!
} else {
_actionInProgress.Add(phase, new BattleResultText(
_menuUI, Font.BATTLE_MISS.ToString(), Color.White,
() => GetModelScreenPos(result.Target).XY(), new Vector2(0, -1),
60,
$"{result.Target.Name} missed"
));
}
}
_effect = new BattleEffectManager(Game, this, nextAction, results, nextAction.Name /*VERY TODO*/);
}
}
protected override void DoRender() {
_renderer.Render();
Renderer.Render();
_menuUI.Render();
_debug?.Render();
}
@ -600,13 +540,13 @@ namespace Braver.Battle {
if (_debugCamera) {
if (input.IsDown(InputKey.Up))
_renderer.View3D.CameraPosition += new Vector3(0, 0, -100);
Renderer.View3D.CameraPosition += new Vector3(0, 0, -100);
if (input.IsDown(InputKey.Down))
_renderer.View3D.CameraPosition += new Vector3(0, 0, 100);
Renderer.View3D.CameraPosition += new Vector3(0, 0, 100);
if (input.IsDown(InputKey.Left))
_renderer.View3D.CameraPosition += new Vector3(100, 0, 0);
Renderer.View3D.CameraPosition += new Vector3(100, 0, 0);
if (input.IsDown(InputKey.Right))
_renderer.View3D.CameraPosition += new Vector3(-100, 0, 0);
Renderer.View3D.CameraPosition += new Vector3(-100, 0, 0);
} else if (input.IsJustDown(InputKey.Menu)) {
NextMenu();
} else if (Targets != null) {

View File

@ -98,6 +98,25 @@ namespace Braver.Battle {
}
public class SpriteManager {
private Dictionary<string, LoadedSprite> _sprites = new(StringComparer.InvariantCultureIgnoreCase);
private FGame _game;
private GraphicsDevice _graphics;
public SpriteManager(FGame game, GraphicsDevice graphics) {
_game = game;
_graphics = graphics;
}
public LoadedSprite Get(string spriteFile, IEnumerable<string> textures) {
string key = spriteFile + "," + string.Join(",", textures);
if (!_sprites.TryGetValue(key, out var sprite))
_sprites[key] = sprite = new LoadedSprite(_game, _graphics, spriteFile, textures);
return sprite;
}
}
public class SpriteRenderer {
private List<(LoadedSprite.Instance sprite, Func<Vector3> pos)> _sprites = new();
@ -109,8 +128,10 @@ namespace Braver.Battle {
_graphics = graphics;
}
public void Add(LoadedSprite sprite, Func<Vector3> getPos) {
_sprites.Add((new LoadedSprite.Instance(sprite), getPos));
public LoadedSprite.Instance Add(LoadedSprite sprite, Func<Vector3> getPos) {
var instance = new LoadedSprite.Instance(sprite);
_sprites.Add((instance, getPos));
return instance;
}
public void FrameStep() {

81
Braver/BraverRazor.cs Normal file
View File

@ -0,0 +1,81 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
using Braver.UI.Layout;
using Ficedula;
using RazorEngineCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Braver {
public interface IBraverTemplateModel {
public FGame Game { get; }
public string SourceCategory { get; }
public string SourceExtension { get; }
}
public class BraverTemplate : RazorEngineTemplateBase {
public FGame GameModel {
get {
switch (Model) {
case FGame game:
return game;
case IBraverTemplateModel model:
return model.Game;
default:
throw new NotSupportedException();
}
}
}
public string Bool(bool b) {
return b.ToString().ToLower();
}
public string Include(string templateName, object model) {
var btemplate = Model as IBraverTemplateModel;
var cache = GameModel.Singleton(() => new RazorLayoutCache(GameModel));
return cache.ApplyPartial(btemplate.SourceCategory, templateName + "." + btemplate.SourceExtension, false, model);
}
}
internal class RazorLayoutCache {
private Dictionary<string, IRazorEngineCompiledTemplate<BraverTemplate>> _templates = new Dictionary<string, IRazorEngineCompiledTemplate<BraverTemplate>>(StringComparer.InvariantCultureIgnoreCase);
private IRazorEngine _razorEngine = new RazorEngine();
private FGame _game;
public FGame Game => _game;
public RazorLayoutCache(FGame game) {
_game = game;
}
public IRazorEngineCompiledTemplate<BraverTemplate> Compile(string category, string razorFile, bool forceReload) {
string key = category + "\\" + razorFile;
if (forceReload || !_templates.TryGetValue(key, out var razor)) {
string template = _game.OpenString(category, razorFile);
_templates[key] = razor = _razorEngine.Compile<BraverTemplate>(template, builder => {
builder.AddAssemblyReference(typeof(RazorLayoutCache));
builder.AddAssemblyReference(typeof(SaveData));
builder.AddAssemblyReference(typeof(Ficedula.FF7.Item));
builder.AddAssemblyReference(typeof(Enumerable));
});
}
return razor;
}
public string ApplyPartial(string category, string razorFile, bool forceReload, object model) {
return Compile(category, razorFile, forceReload).Run(template => {
template.Model = model;
});
}
}
}

View File

@ -67,8 +67,8 @@ namespace Braver.Field {
private UI.Layout.Image iPointer;
public void Reset(FGame game) {
var cache = game.Singleton(() => new UI.Layout.RazorLayoutCache(game));
string xml = cache.ApplyPartial("dialog", false, this);
var cache = game.Singleton(() => new RazorLayoutCache(game));
string xml = cache.ApplyPartial("layout", "dialog.xml", false, this);
Visual = Serialisation.Deserialise<UI.Layout.Component>(xml) as UI.Layout.Container;
lText = Visual.Children.Single(c => c.ID == nameof(lText)) as UI.Layout.Label;
lVariable = Visual.Children.SingleOrDefault(c => c.ID == nameof(lVariable)) as UI.Layout.Label;

View File

@ -148,6 +148,15 @@ namespace Braver {
return new Vector2(v.X, v.Y);
}
public static Vector3 Parse3(string s) {
string[] parts = s.Split('/');
return new Vector3(
float.Parse(parts[0]),
float.Parse(parts[1]),
float.Parse(parts[2])
);
}
public static bool LineCircleIntersect(Vector2 line0, Vector2 line1, Vector2 center, float radius) {
Vector2 ac = center - line0;
Vector2 ab = line1 - line0;

View File

@ -377,7 +377,7 @@ namespace Braver.UI.Layout {
public string Description { get; set; }
}
public abstract class LayoutModel {
public abstract class LayoutModel : IBraverTemplateModel {
protected Dictionary<string, Component> _components = new Dictionary<string, Component>(StringComparer.InvariantCultureIgnoreCase);
protected FGame _game;
@ -402,6 +402,9 @@ namespace Braver.UI.Layout {
public virtual bool IsRazorModel => false;
string IBraverTemplateModel.SourceCategory => "layout";
string IBraverTemplateModel.SourceExtension => "xml";
private class FocusEntry {
public string ContainerID { get; set; }
public string FocusID { get; set; }
@ -509,66 +512,14 @@ namespace Braver.UI.Layout {
}
}
public class BraverTemplate : RazorEngineTemplateBase {
//public new FGame Model { get; set; }
public FGame GameModel {
get {
switch (Model) {
case FGame game:
return game;
case LayoutModel model:
return model.Game;
default:
throw new NotSupportedException();
}
}
}
public string Bool(bool b) {
return b.ToString().ToLower();
}
public string Include(string templateName, object model) {
var cache = GameModel.Singleton(() => new RazorLayoutCache(GameModel));
return cache.ApplyPartial(templateName, false, model);
}
}
internal class RazorLayoutCache {
private Dictionary<string, IRazorEngineCompiledTemplate<BraverTemplate>> _templates = new Dictionary<string, IRazorEngineCompiledTemplate<BraverTemplate>>(StringComparer.InvariantCultureIgnoreCase);
private IRazorEngine _razorEngine = new RazorEngine();
private FGame _game;
public RazorLayoutCache(FGame game) {
_game = game;
}
public IRazorEngineCompiledTemplate<BraverTemplate> Compile(string layout, bool forceReload) {
if (forceReload || !_templates.TryGetValue(layout, out var razor)) {
string template = _game.OpenString("layout", layout + ".xml");
_templates[layout] = razor = _razorEngine.Compile<BraverTemplate>(template, builder => {
builder.AddAssemblyReference(typeof(RazorLayoutCache));
builder.AddAssemblyReference(typeof(SaveData));
builder.AddAssemblyReference(typeof(Ficedula.FF7.Item));
builder.AddAssemblyReference(typeof(Enumerable));
});
}
return razor;
}
public Layout Apply(string layout, bool forceReload, object model = null) {
string xml = Compile(layout, forceReload).Run(template => {
template.Model = model ?? _game;
internal static class LayoutRazor {
public static Layout ApplyLayout(this RazorLayoutCache cache, string layout, bool forceReload, object model = null) {
string xml = cache.Compile("layout", layout + ".xml", forceReload).Run(template => {
template.Model = model ?? cache.Game;
});
return Serialisation.Deserialise<Layout>(xml);
}
public string ApplyPartial(string layout, bool forceReload, object model) {
return Compile(layout, forceReload).Run(template => {
template.Model = model;
});
}
}
public class LayoutScreen : Screen, ILayoutScreen {
@ -580,7 +531,7 @@ namespace Braver.UI.Layout {
private static Task _backgroundLoad;
public static void BeginBackgroundLoad(FGame g, string layout) {
var cache = g.Singleton(() => new RazorLayoutCache(g));
_backgroundLoad = Task.Run(() => cache.Compile(layout, true));
_backgroundLoad = Task.Run(() => cache.Compile("layout", layout + ".xml", true));
}
public override Color ClearColor => Color.Black;
@ -613,7 +564,7 @@ namespace Braver.UI.Layout {
_backgroundLoad.Wait();
_backgroundLoad = null;
}
_layout = g.Singleton(() => new RazorLayoutCache(g)).Apply(_layoutFile, false, _model.IsRazorModel ? _model : null);
_layout = g.Singleton(() => new RazorLayoutCache(g)).ApplyLayout(_layoutFile, false, _model.IsRazorModel ? _model : null);
_model.Init(_layout);
_ui = new UIBatch(graphics, g);
if (!_isEmbedded) g.Net.Send(new Net.ScreenReadyMessage());
@ -622,7 +573,7 @@ namespace Braver.UI.Layout {
public void Reload(bool forceReload = false) {
_model.Created(Game, this);
_layout = Game.Singleton(() => new RazorLayoutCache(Game)).Apply(_layoutFile, forceReload, _model.IsRazorModel ? _model : null);
_layout = Game.Singleton(() => new RazorLayoutCache(Game)).ApplyLayout(_layoutFile, forceReload, _model.IsRazorModel ? _model : null);
_model.Init(_layout);
Plugins.Call(ui => ui.Reloaded());
}
@ -750,7 +701,7 @@ namespace Braver.UI.Layout {
IComponent ILayoutScreen.Load(string templateName, object? model) {
var cache = Game.Singleton(() => new RazorLayoutCache(Game));
string xml = cache.ApplyPartial(templateName, false, model ?? _model);
string xml = cache.ApplyPartial("layout", templateName + ".xml", false, model ?? _model);
return Serialisation.Deserialise<Component>(xml);
}

View File

@ -22,6 +22,7 @@ DATA FILE? kernel %ff7%\data\lang-en\kernel
DATA FILE root %ff7%
DATA FILE? battle %braver%\battle
DATA FILE? field %braver%\field
DATA FILE? layout %braver%\layout
DATA FILE? movies %braver%\movies

View File

@ -9,6 +9,7 @@ using Ficedula.FF7.Exporters;
Console.WriteLine("F7Cmd");
/*
using (var lgp = new Ficedula.FF7.LGPFile(@"C:\games\FF7\data\battle\magic.lgp")) {
foreach(string s in lgp.Filenames.Where(s => s.Contains("bio")))
@ -25,24 +26,8 @@ using (var lgp = new Ficedula.FF7.LGPFile(@"C:\games\FF7\data\battle\magic.lgp")
}
}
}
var tex = new Ficedula.FF7.TexFile(lgp.Open("fire00.tex"));
foreach (int pal in Enumerable.Range(0, tex.Palettes.Count))
using (var s = File.OpenWrite(@$"C:\temp\fire00#{pal}.png"))
Ficedula.FF7.Exporters.TexFileUtil.ToBitmap(tex, pal).Encode(SkiaSharp.SKEncodedImageFormat.Png, 100)
.SaveTo(s);
/*
foreach (string fn in new[] { "fire_1.s", "fire00.tex", "fire01.tex", "fire_2.s" }) {
using (var s = lgp.Open(fn)) {
using (var fs = File.OpenWrite(@"C:\temp\" + fn))
s.CopyTo(fs);
}
}
*/
}
*/
/*
foreach(string file in Directory.GetFiles(@"C:\temp\wm_us", "*.a")) {

View File

@ -0,0 +1,66 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ficedula.FF7.Exporters {
public abstract class DataSource {
public abstract bool Exists(string name);
public abstract Stream Open(string name);
public abstract IEnumerable<string> AllFiles { get; }
public virtual Stream TryOpen(string name) {
if (Exists(name))
return Open(name);
else
return null;
}
private class FileDataSource : DataSource {
private string _root;
public override IEnumerable<string> AllFiles => Directory.GetFiles(_root)
.Select(fn => fn.Substring(_root.Length).TrimStart(Path.DirectorySeparatorChar));
public FileDataSource(string root) {
_root = root;
}
public override bool Exists(string name) => File.Exists(Path.Combine(_root, name));
public override Stream Open(string name) => File.OpenRead(Path.Combine(_root, name));
}
private class LGPDataSource : DataSource, IDisposable {
private LGPFile _lgp;
public override IEnumerable<string> AllFiles => _lgp.Filenames;
public LGPDataSource(string file) {
_lgp = new LGPFile(File.OpenRead(file));
}
public void Dispose() {
_lgp.Dispose();
}
public override bool Exists(string name) => _lgp.Exists(name);
public override Stream Open(string name) => _lgp.Open(name);
}
public static DataSource Create(string source) {
if (Directory.Exists(source))
return new FileDataSource(source);
else
return new LGPDataSource(source);
}
}
}

View File

@ -151,11 +151,13 @@ namespace Braver.Tolk {
DavyKager.Tolk.Speak(action, false);
}
/*
public void BattleActionResult(IInProgress result) {
string s = result.Description;
if (!string.IsNullOrEmpty(s))
DavyKager.Tolk.Speak(s, false);
}
*/
public void Showing(int window, int tag, IEnumerable<string> text) {
//

View File

@ -0,0 +1,24 @@
PreloadSprite fire fi_a01.s fire00.tex fire01.tex
PreloadSfx 8
@foreach(var text in Model.Action.QueuedText) {
@:!txt Text @Model.Expand(text)
@:Wait !txt
}
!cast Anim source 17
Wait !cast
#Camera
Sfx 8
@foreach(var result in Model.Results) {
@:!flames Sprite fire @result.Target.ID
@:Result @result.Target.ID
if (result.Hit) {
}
}
Wait !flames
Resume source
Wait !cast
ApplyResults