Skip to content

Commit

Permalink
Scripting|Model Renderer|libcore: Script callbacks for model state ch…
Browse files Browse the repository at this point in the history
…ange

Added de::ConditionalTrigger for issuing a callback under the
specified circumstances. Configured with a Variable.

Model assets can use `notifiedStates` and `onStateChange()` to observe
when the mobj changes to (a) specific state(s).

Value::is<>() was removed, now should only use de::is<>.

Renamed de::Scheduler to de::Timeline.
  • Loading branch information
skyjake committed Jun 18, 2017
1 parent a328008 commit 391a3eb
Show file tree
Hide file tree
Showing 30 changed files with 330 additions and 114 deletions.
6 changes: 3 additions & 3 deletions doomsday/apps/client/include/render/model.h
Expand Up @@ -21,7 +21,7 @@

#include <de/Record>
#include <de/ModelDrawable>
#include <de/Scheduler>
#include <de/Timeline>
#include <de/MultiAtlas>

#include <QHash>
Expand Down Expand Up @@ -50,7 +50,7 @@ struct Model : public de::ModelDrawable
{
de::String name; ///< Name of the sequence.
de::Record const *def; ///< Record describing the sequence (in asset metadata).
de::Scheduler *timeline = nullptr; ///< Script timeline (owned).
de::Timeline *timeline = nullptr; ///< Script timeline (owned).
de::String sharedTimeline; ///< Name of shared timeline (if specified).

AnimSequence(de::String const &n, de::Record const &d);
Expand Down Expand Up @@ -102,7 +102,7 @@ struct Model : public de::ModelDrawable
StateAnims animations;

/// Shared timelines (not sequence-specific). Owned.
QHash<de::String, de::Scheduler *> timelines;
QHash<de::String, de::Timeline *> timelines;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(Model::Flags)
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/include/render/modelrenderer.h
Expand Up @@ -23,7 +23,7 @@
#include <de/ModelDrawable>
#include <de/ModelBank>
#include <de/GLState>
#include <de/Scheduler>
#include <de/Timeline>
#include <de/MultiAtlas>

#include <QList>
Expand Down
3 changes: 2 additions & 1 deletion doomsday/apps/client/include/render/stateanimator.h
Expand Up @@ -29,7 +29,8 @@
namespace render {

/**
* State-based object animator for `ModelDrawable`s.
* State-based object animator for `ModelDrawable`s. Triggers animation sequences
* based on state changes and damage points received by the owner.
*
* Used for both mobjs and psprites. The state and movement of the object
* determine which animation sequences are started.
Expand Down
6 changes: 3 additions & 3 deletions doomsday/apps/client/src/configprofiles.cpp
Expand Up @@ -221,11 +221,11 @@ DENG2_PIMPL(ConfigProfiles)
DENG2_ASSERT(confDefaults.has(name));

Variable const &var = confDefaults[name];
if (var.value().is<NumberValue>())
if (is<NumberValue>(var.value()))
{
return var.value().asNumber();
}
else if (var.value().is<TextValue>())
else if (is<TextValue>(var.value()))
{
return var.value().asText();
}
Expand Down Expand Up @@ -270,7 +270,7 @@ DENG2_PIMPL(ConfigProfiles)

case ConfigVariable: {
Value const &cfgValue = Config::get(st.name).value();
if (cfgValue.is<NumberValue>())
if (is<NumberValue>(cfgValue))
{
val = cfgValue.asNumber();
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/network/base/masterserver.cpp
Expand Up @@ -216,7 +216,7 @@ bool MasterWorker::parseResponse(QByteArray const &response)
try
{
std::unique_ptr<Value> entryValue(Value::constructFrom(entry));
if (!entryValue->is<RecordValue>())
if (!is<RecordValue>(*entryValue))
{
LOG_NET_WARNING("Server information was in unexpected format");
continue;
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/network/serverlink.cpp
Expand Up @@ -119,7 +119,7 @@ DENG2_PIMPL(ServerLink)
{
QVariant const response = parseJSON(String::fromUtf8(reply.mid(5)));
std::unique_ptr<Value> rec(Value::constructFrom(response.toMap()));
if (!rec->is<RecordValue>())
if (!is<RecordValue>(*rec))
{
throw Error("ServerLink::handleInfoResponse", "Failed to parse response contents");
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/render/model.cpp
Expand Up @@ -31,7 +31,7 @@ Model::AnimSequence::AnimSequence(String const &name, Record const &def)
// Parse timeline events.
if (def.hasSubrecord(DEF_TIMELINE))
{
timeline = new Scheduler;
timeline = new Timeline;
timeline->addFromInfo(def.subrecord(DEF_TIMELINE));
}
else if (def.hasMember(DEF_TIMELINE))
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/render/modelloader.cpp
Expand Up @@ -508,7 +508,7 @@ DENG2_PIMPL(ModelLoader)
auto timelines = ScriptedInfo::subrecordsOfType(DEF_TIMELINE, asset.subrecord(DEF_ANIMATION));
DENG2_FOR_EACH_CONST(Record::Subrecords, timeline, timelines)
{
Scheduler *scheduler = new Scheduler;
Timeline *scheduler = new Timeline;
scheduler->addFromInfo(*timeline.value());
model.timelines[timeline.key()] = scheduler;
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/client/src/render/playerweaponanimator.cpp
Expand Up @@ -63,7 +63,7 @@ DENG2_PIMPL_NOREF(PlayerWeaponAnimator)
auto &model = modelBank().model<Model>(identifier);
model.audienceForDeletion() += this;
animator.reset(new StateAnimator(identifier, model));
animator->setOwnerNamespace(player->info(), QStringLiteral("PLAYER"));
animator->setOwnerNamespace(player->info(), QStringLiteral("__player__"));
}
else
{
Expand Down
82 changes: 65 additions & 17 deletions doomsday/apps/client/src/render/stateanimator.cpp
Expand Up @@ -25,13 +25,16 @@

#include <doomsday/world/thinkerdata.h>

#include <de/ConditionalTrigger>
#include <de/ScriptedInfo>
#include <de/ScriptSystem>
#include <de/RecordValue>
#include <de/NoneValue>
#include <de/NativePointerValue>
#include <de/GLUniform>

#include <QSet>

using namespace de;

namespace render {
Expand All @@ -53,6 +56,8 @@ static String const VAR_ASSET ("__asset__"); // runtime reference to
static String const VAR_ENABLED ("enabled");
static String const VAR_MATERIAL ("material");

static String const VAR_NOTIFIED_STATES("notifiedStates");

static String const PASS_GLOBAL ("");
static String const DEFAULT_MATERIAL ("default");

Expand All @@ -75,14 +80,14 @@ DENG2_PIMPL(StateAnimator)

LoopMode looping = NotLooping;
int priority = ANIM_DEFAULT_PRIORITY;
Scheduler const *timeline = nullptr; // owned by ModelRenderer::AnimSequence
std::unique_ptr<Scheduler::Clock> clock;
Timeline const *timeline = nullptr; // owned by ModelRenderer::AnimSequence
std::unique_ptr<Timeline::Clock> clock;
TimeDelta overrideDuration = -1.0;

Sequence() {}

Sequence(int animationId, String const &rootNode, LoopMode looping, int priority,
Scheduler const *timeline = nullptr)
Timeline const *timeline = nullptr)
: looping(looping)
, priority(priority)
, timeline(timeline)
Expand Down Expand Up @@ -172,15 +177,31 @@ DENG2_PIMPL(StateAnimator)
typedef QHash<String, AnimVar *> AnimVars;
AnimVars animVars;

Impl(Public *i, DotPath const &id) : Base(i)
/// Optional script callback for chosen states.
struct StateCallback : public ConditionalTrigger
{
names.add(Record::VAR_NATIVE_SELF).set(new NativePointerValue(thisPublic)).setReadOnly();
names.addSuperRecord(ScriptSystem::builtInClass(QStringLiteral("Render"),
QStringLiteral("StateAnimator")));
names.addText(VAR_ID, id).setReadOnly();
names.add(VAR_ASSET).set(new RecordValue(App::asset(id).accessedRecord())).setReadOnly();
Record &names;

StateCallback(Record &names) : names(names)
{
setCondition(names[VAR_NOTIFIED_STATES]);
}

initVariables();
void handleTriggered(String const &trigger) override
{
Record ns;
ns.add(QStringLiteral("self")).set(new RecordValue(names));
Process::scriptCall(Process::IgnoreResult, ns,
QStringLiteral("self.__asset__.onStateChange"),
"$self", // StateAnimator instance
trigger); // new state
}
};
std::unique_ptr<StateCallback> stateCallback;

Impl(Public *i, DotPath const &assetId) : Base(i)
{
initVariables(assetId);

// Set up the model drawing parameters.
if (!self().model().passes.isEmpty())
Expand All @@ -205,8 +226,30 @@ DENG2_PIMPL(StateAnimator)
deinitVariables();
}

void initVariables()
void initVariables(DotPath const &assetId)
{
// Initialize the StateAnimator script object.
names.add(Record::VAR_NATIVE_SELF).set(new NativePointerValue(thisPublic)).setReadOnly();
names.addSuperRecord(ScriptSystem::builtInClass(QStringLiteral("Render"),
QStringLiteral("StateAnimator")));
names.addText(VAR_ID, assetId).setReadOnly();
Record const &assetDef = App::asset(assetId).accessedRecord();
names.add(VAR_ASSET).set(new RecordValue(assetDef)).setReadOnly();
if (assetDef.has(VAR_NOTIFIED_STATES))
{
// Use the initial value for state callbacks.
names.add(VAR_NOTIFIED_STATES).set(assetDef.get(VAR_NOTIFIED_STATES));
}
else
{
// The states to notify can be chosen later.
names.addArray(VAR_NOTIFIED_STATES);
}
if (assetDef.has(QStringLiteral("onStateChange")))
{
stateCallback.reset(new StateCallback(names));
}

int const passCount = self().model().passes.size();

// Clear lookups affected by the variables.
Expand Down Expand Up @@ -523,7 +566,7 @@ DENG2_PIMPL(StateAnimator)
anim.apply(spec);
if (anim.timeline)
{
anim.clock.reset(new Scheduler::Clock(*anim.timeline, &names));
anim.clock.reset(new Timeline::Clock(*anim.timeline, &names));
}
applyFlagOperation(anim.flags, Sequence::ClampToDuration,
anim.looping == Sequence::Looping? UnsetFlags : SetFlags);
Expand All @@ -547,12 +590,12 @@ void StateAnimator::setOwnerNamespace(Record &names, String const &varName)
d->names.add(d->ownerNamespaceVarName).set(new RecordValue(names));

// Call the onInit() function if there is one.
if (d->names.has(VAR_ASSET + QStringLiteral(".onInit")))
if (d->names.has(QStringLiteral("__asset__.onInit")))
{
Record ns;
ns.add(QStringLiteral("self")).set(new RecordValue(d->names));
Process::scriptCall(Process::IgnoreResult, ns,
"self." + VAR_ASSET + ".onInit",
"self.__asset__.onInit",
"$self");
}
}
Expand All @@ -566,6 +609,11 @@ void StateAnimator::triggerByState(String const &stateName)
{
using Sequence = Impl::Sequence;

if (d->stateCallback)
{
d->stateCallback->tryTrigger(stateName);
}

// No animations can be triggered if none are available.
auto const *stateAnims = &model().animations;
if (!stateAnims) return;
Expand Down Expand Up @@ -603,7 +651,7 @@ void StateAnimator::triggerByState(String const &stateName)
int const priority = seq.def->geti(DEF_PRIORITY, ANIM_DEFAULT_PRIORITY);

// Look up the timeline.
Scheduler *timeline = seq.timeline;
Timeline *timeline = seq.timeline;
if (!seq.sharedTimeline.isEmpty())
{
auto tl = model().timelines.constFind(seq.sharedTimeline);
Expand Down Expand Up @@ -663,7 +711,7 @@ void StateAnimator::triggerDamage(int points, struct mobj_s const *inflictor)
* variable holds a direct pointer to the asset definition, where the
* function is defined.
*/
if (d->names.has(VAR_ASSET + QStringLiteral(".onDamage")))
if (d->names.has(QStringLiteral("__asset__.onDamage")))
{
/*
* We need to provide the StateAnimator instance to the script as an
Expand All @@ -673,7 +721,7 @@ void StateAnimator::triggerDamage(int points, struct mobj_s const *inflictor)
Record ns;
ns.add(QStringLiteral("self")).set(new RecordValue(d->names));
Process::scriptCall(Process::IgnoreResult, ns,
"self." + VAR_ASSET + ".onDamage",
QStringLiteral("self.__asset__.onDamage"),
"$self", points,
inflictor? &THINKER_DATA(inflictor->thinker, ThinkerData) :
nullptr);
Expand Down
6 changes: 3 additions & 3 deletions doomsday/apps/client/src/ui/editors/modelasseteditor.cpp
Expand Up @@ -391,17 +391,17 @@ DENG_GUI_PIMPL(ModelAssetEditor)
g->addSlider(var, range, step, precision);
}
}
else if (var.value().is<TextValue>())
else if (is<TextValue>(var.value()))
{
g->addLabel(varLabel(label));
g->addLineEdit(var);
}
else if (var.value().is<AnimationValue>())
else if (is<AnimationValue>(var.value()))
{
g->addLabel(varLabel(label));
g->addSlider(var, range, step, precision);
}
else if (descend && var.value().is<RecordValue>())
else if (descend && is<RecordValue>(var.value()))
{
populateGroup(g, var.valueAsRecord(), descend, label);
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/libdoomsday/src/defs/dedregister.cpp
Expand Up @@ -156,7 +156,7 @@ DENG2_PIMPL(DEDRegister)

bool isEmptyKeyValue(Value const &value) const
{
return value.is<TextValue>() && value.asText().isEmpty();
return is<TextValue>(value) && value.asText().isEmpty();
}

bool isValidKeyValue(Value const &value) const
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/libdoomsday/src/gamestatefolder.cpp
Expand Up @@ -455,7 +455,7 @@ String GameStateFolder::Metadata::asTextWithInfoSyntax() const
{
Value const &value = i.value()->value();
String valueAsText = value.asText();
if (value.is<Value::Text>())
if (is<Value::Text>(value))
{
valueAsText = "\"" + valueAsText.replace("\"", "''") + "\"";
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/apps/libdoomsday/src/urivalue.cpp
Expand Up @@ -63,7 +63,7 @@ bool UriValue::contains(Value const &value) const
{
// We are able to look for substrings within the text, without applying automatic
// type conversions.
if (value.is<TextValue>())
if (is<TextValue>(value))
{
return _uri.asText().indexOf(value.asText(), Qt::CaseSensitive) >= 0;
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/sdk/libappfw/src/widgets/scriptcommandwidget.cpp
Expand Up @@ -157,7 +157,7 @@ void ScriptCommandWidget::executeCommand(String const &text)
try
{
Value const &result = d->process.context().evaluator().result();
if (!result.is<NoneValue>())
if (!is<NoneValue>(result))
{
String msg = DENG2_CHAR_RIGHT_DOUBLEARROW " " _E(>)_E(m) + result.asText();
LOG_SCR_MSG(msg);
Expand Down
4 changes: 2 additions & 2 deletions doomsday/sdk/libappfw/src/widgets/variablechoicewidget.cpp
Expand Up @@ -42,7 +42,7 @@ DENG2_OBSERVES(Variable, Change )
{
if (!var) return;

if (var->value().is<TextValue>())
if (is<TextValue>(var->value()))
{
self().setSelected(self().items().findData(var->value().asText()));
}
Expand All @@ -57,7 +57,7 @@ DENG2_OBSERVES(Variable, Change )
if (!var) return;

var->audienceForChange() -= this;
if (var->value().is<TextValue>())
if (is<TextValue>(var->value()))
{
var->set(TextValue(self().selectedItem().data().toString()));
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/sdk/libappfw/src/widgets/variablesliderwidget.cpp
Expand Up @@ -94,7 +94,7 @@ VariableSliderWidget::VariableSliderWidget(Variable &variable, Ranged const &ran
: SliderWidget(name)
, d(new Impl(this, variable))
{
if (!variable.value().is<NumberValue>())
if (!is<NumberValue>(variable.value()))
{
// Animation is the only other supported type.
d->valueType = VariableSliderWidget::Animation;
Expand Down
1 change: 1 addition & 0 deletions doomsday/sdk/libcore/include/de/ConditionalTrigger
@@ -0,0 +1 @@
#include "data/conditionaltrigger.h"
1 change: 1 addition & 0 deletions doomsday/sdk/libcore/include/de/Timeline
@@ -0,0 +1 @@
#include "scriptsys/timeline.h"

0 comments on commit 391a3eb

Please sign in to comment.