Torque3D Documentation / _generateds / afxPhraseEffect.cpp

afxPhraseEffect.cpp

Engine/source/afx/ce/afxPhraseEffect.cpp

More...

Public Defines

define
myOffset(field) (field, )

Public Variables

Public Functions

ConsoleDocClass(afxPhraseEffectData , "@brief A datablock that specifies <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Phrase Effect, <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> grouping of other <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">effects.\n\n</a>" "A Phrase Effect is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> grouping or phrase of effects that do nothing until certain trigger events occur. It 's like having <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> whole " "Effectron organized as an individual effect." "\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Phrase effects can respond <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> number of different kinds of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">triggers:\n</a>" " -- <a href="/coding/class/classplayer/">Player</a> triggers such as footsteps, jumps , landings , and idle <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">triggers.\n</a>" " -- Arbitrary animation triggers on dts-based scene <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n</a>" " -- Arbitrary trigger bits assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> active choreographer objects." "\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxEffects\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )
DefineEngineMethod(afxPhraseEffectData , pushEffect , void , (afxEffectBaseData *effectData) , "Add <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> child effect <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> phrase effect datablock. Argument can be an afxEffectWrappperData or an <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxEffectGroupData.\n</a>" )
ImplementEnumType(afxPhraseEffect_MatchType , "Possible phrase effect match <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )
ImplementEnumType(afxPhraseEffect_PhraseType , "Possible phrase effect <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )
ImplementEnumType(afxPhraseEffect_StateType , "Possible phrase effect state <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )

Detailed Description

Public Defines

myOffset(field) (field, )

Public Variables

 EndImplementEnumType 

Public Functions

ConsoleDocClass(afxPhraseEffectData , "@brief A datablock that specifies <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Phrase Effect, <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> grouping of other <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">effects.\n\n</a>" "A Phrase Effect is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> grouping or phrase of effects that do nothing until certain trigger events occur. It 's like having <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> whole " "Effectron organized as an individual effect." "\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Phrase effects can respond <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> number of different kinds of <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">triggers:\n</a>" " -- <a href="/coding/class/classplayer/">Player</a> triggers such as footsteps, jumps , landings , and idle <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">triggers.\n</a>" " -- Arbitrary animation triggers on dts-based scene <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n</a>" " -- Arbitrary trigger bits assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> active choreographer objects." "\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxEffects\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">AFX\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )

DefineEngineMethod(afxPhraseEffectData , pushEffect , void , (afxEffectBaseData *effectData) , "Add <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> child effect <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> phrase effect datablock. Argument can be an afxEffectWrappperData or an <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxEffectGroupData.\n</a>" )

IMPLEMENT_CO_DATABLOCK_V1(afxPhraseEffectData )

ImplementEnumType(afxPhraseEffect_MatchType , "Possible phrase effect match <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )

ImplementEnumType(afxPhraseEffect_PhraseType , "Possible phrase effect <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )

ImplementEnumType(afxPhraseEffect_StateType , "Possible phrase effect state <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">types.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">afxPhraseEffect\n\n</a>" )

  1
  2
  3//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
  4// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
  5// Copyright (C) 2015 Faust Logic, Inc.
  6//
  7// Permission is hereby granted, free of charge, to any person obtaining a copy
  8// of this software and associated documentation files (the "Software"), to
  9// deal in the Software without restriction, including without limitation the
 10// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 11// sell copies of the Software, and to permit persons to whom the Software is
 12// furnished to do so, subject to the following conditions:
 13//
 14// The above copyright notice and this permission notice shall be included in
 15// all copies or substantial portions of the Software.
 16//
 17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 23// IN THE SOFTWARE.
 24//
 25//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 26
 27#include "afx/arcaneFX.h"
 28
 29#include "console/engineAPI.h"
 30
 31#include "afx/afxEffectWrapper.h"
 32#include "afx/ce/afxPhraseEffect.h"
 33
 34//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 35// afxPhraseEffectData::ewValidator
 36//
 37// When an effect is added using "addEffect", this validator intercepts the value
 38// and adds it to the dynamic effects list. 
 39//
 40void afxPhraseEffectData::ewValidator::validateType(SimObject* object, void* typePtr)
 41{
 42  afxPhraseEffectData* eff_data = dynamic_cast<afxPhraseEffectData*>(object);
 43  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
 44
 45  if (eff_data && ew)
 46  {
 47    eff_data->fx_list.push_back(*ew);
 48    *ew = 0;
 49  }
 50}
 51
 52//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
 53// afxPhraseEffectData
 54
 55IMPLEMENT_CO_DATABLOCK_V1(afxPhraseEffectData);
 56
 57ConsoleDocClass( afxPhraseEffectData,
 58   "@brief A datablock that specifies a Phrase Effect, a grouping of other effects.\n\n"
 59
 60   "A Phrase Effect is a grouping or phrase of effects that do nothing until certain trigger events occur. It's like having a whole "
 61   "Effectron organized as an individual effect."
 62   "\n\n"
 63
 64   "Phrase effects can respond to a number of different kinds of triggers:\n"
 65   "  -- Player triggers such as footsteps, jumps, landings, and idle triggers.\n"
 66   "  -- Arbitrary animation triggers on dts-based scene objects.\n"
 67   "  -- Arbitrary trigger bits assigned to active choreographer objects."
 68   "\n\n"
 69
 70   "@ingroup afxEffects\n"
 71   "@ingroup AFX\n"
 72   "@ingroup Datablocks\n"
 73);
 74
 75afxPhraseEffectData::afxPhraseEffectData()
 76{
 77  duration = 0.0f;
 78  n_loops = 1;
 79
 80  // dummy entry holds effect-wrapper pointer while a special validator
 81  // grabs it and adds it to an appropriate effects list
 82  dummy_fx_entry = NULL;
 83
 84  // marked true if datablock ids need to
 85  // be converted into pointers
 86  do_id_convert = false;
 87
 88  trigger_mask = 0;
 89  match_type = MATCH_ANY;
 90  match_state = STATE_ON;
 91  phrase_type = PHRASE_TRIGGERED;
 92
 93  no_choreographer_trigs = false;
 94  no_cons_trigs = false;
 95  no_player_trigs = false;
 96
 97  on_trig_cmd = ST_NULLSTRING;
 98}
 99
100afxPhraseEffectData::afxPhraseEffectData(const afxPhraseEffectData& other, bool temp_clone) : GameBaseData(other, temp_clone)
101{
102  duration = other.duration;
103  n_loops = other.n_loops;
104  dummy_fx_entry = other.dummy_fx_entry;
105  do_id_convert = other.do_id_convert; // --
106  trigger_mask = other.trigger_mask;
107  match_type = other.match_type;
108  match_state = other.match_state;
109  phrase_type = other.phrase_type;
110  no_choreographer_trigs = other.no_choreographer_trigs;
111  no_cons_trigs = other.no_cons_trigs;
112  no_player_trigs = other.no_player_trigs;
113  on_trig_cmd = other.on_trig_cmd;
114
115  // fx_list; // -- ??
116}
117
118void afxPhraseEffectData::reloadReset()
119{
120  fx_list.clear();
121}
122
123ImplementEnumType( afxPhraseEffect_MatchType, "Possible phrase effect match types.\n" "@ingroup afxPhraseEffect\n\n" )
124   { afxPhraseEffectData::MATCH_ANY,   "any",      "..." },
125   { afxPhraseEffectData::MATCH_ALL,   "all",      "..." },
126EndImplementEnumType;
127
128ImplementEnumType( afxPhraseEffect_StateType, "Possible phrase effect state types.\n" "@ingroup afxPhraseEffect\n\n" )
129   { afxPhraseEffectData::STATE_ON,           "on",      "..." },
130   { afxPhraseEffectData::STATE_OFF,          "off",     "..." },
131   { afxPhraseEffectData::STATE_ON_AND_OFF,   "both",    "..." },
132EndImplementEnumType;
133
134ImplementEnumType( afxPhraseEffect_PhraseType, "Possible phrase effect types.\n" "@ingroup afxPhraseEffect\n\n" )
135   { afxPhraseEffectData::PHRASE_TRIGGERED,   "triggered",     "..." },
136   { afxPhraseEffectData::PHRASE_CONTINUOUS,  "continuous",    "..." },
137EndImplementEnumType;
138
139#define myOffset(field) Offset(field, afxPhraseEffectData)
140
141void afxPhraseEffectData::initPersistFields()
142{
143  addField("duration",    TypeF32,      myOffset(duration),
144    "Specifies a duration for the phrase-effect. If set to infinity, the phrase-effect "
145    "needs to have a phraseType of continuous. Set infinite duration using "
146    "$AFX::INFINITE_TIME.");
147  addField("numLoops",    TypeS32,      myOffset(n_loops),
148    "Specifies the number of times the phrase-effect should loop. If set to infinity, "
149    "the phrase-effect needs to have a phraseType of continuous. Set infinite looping "
150    "using $AFX::INFINITE_REPEATS.");
151  addField("triggerMask", TypeS32,      myOffset(trigger_mask),
152    "Sets which bits to consider in the current trigger-state which consists of 32 "
153    "trigger-bits combined from (possibly overlapping) player trigger bits, constraint "
154    "trigger bits, and choreographer trigger bits.");
155
156  addField("matchType", TYPEID<afxPhraseEffectData::MatchType>(), myOffset(match_type),
157    "Selects what combination of bits in triggerMask lead to a trigger. When set to "
158    "'any', any bit in triggerMask matching the current trigger-state will cause a "
159    "trigger. If set to 'all', every bit in triggerMask must match the trigger-state. "
160    "Possible values: any or all.");
161  addField("matchState", TYPEID<afxPhraseEffectData::StateType>(), myOffset(match_state),
162    "Selects which bit-state(s) of bits in the triggerMask to consider when comparing to "
163    "the current trigger-state. Possible values: on, off, or both.");
164  addField("phraseType", TYPEID<afxPhraseEffectData::PhraseType>(), myOffset(phrase_type),
165    "Selects between triggered and continuous types of phrases. When set to 'triggered', "
166    "the phrase-effect is triggered when the relevant trigger-bits change state. When set "
167    "to 'continuous', the phrase-effect will stay active as long as the trigger-bits "
168    "remain in a matching state. Possible values: triggered or continuous.");
169
170  addField("ignoreChoreographerTriggers",   TypeBool,  myOffset(no_choreographer_trigs),
171    "When true, trigger-bits on the choreographer will be ignored.");
172  addField("ignoreConstraintTriggers",      TypeBool,  myOffset(no_cons_trigs),
173    "When true, animation triggers from dts-based constraint source objects will be "
174    "ignored.");
175  addField("ignorePlayerTriggers",          TypeBool,  myOffset(no_player_trigs),
176    "When true, Player-specific triggers from Player-derived constraint source objects "
177    "will be ignored.");
178
179  addField("onTriggerCommand",    TypeString,   myOffset(on_trig_cmd),
180    "Like a field substitution statement without the leading '$$' token, this eval "
181    "statement will be executed when a trigger occurs. Any '%%' and '##'  tokens will be "
182    "substituted.");
183
184  // effect lists
185  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
186  static ewValidator emptyValidator(0);  
187  addFieldV("addEffect",  TYPEID< afxEffectBaseData >(),  myOffset(dummy_fx_entry), &emptyValidator,
188    "A field macro which adds an effect wrapper datablock to a list of effects associated "
189    "with the phrase-effect's single phrase. Unlike other fields, addEffect follows an "
190    "unusual syntax. Order is important since the effects will resolve in the order they "
191    "are added to each list.");
192
193  Parent::initPersistFields();
194
195  // disallow some field substitutions
196  disableFieldSubstitutions("addEffect");
197}
198
199bool afxPhraseEffectData::onAdd()
200{
201  if (Parent::onAdd() == false)
202    return false;
203
204  return true;
205}
206
207void afxPhraseEffectData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
208{
209  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
210  for (int i = 0; i < fx.size(); i++)
211    writeDatablockID(stream, fx[i], packed);
212}
213
214void afxPhraseEffectData::unpack_fx(BitStream* stream, afxEffectList& fx)
215{
216  fx.clear();
217  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
218  for (int i = 0; i < n_fx; i++)
219    fx.push_back((afxEffectWrapperData*)(uintptr_t)readDatablockID(stream));
220}
221
222void afxPhraseEffectData::packData(BitStream* stream)
223{
224  Parent::packData(stream);
225
226  stream->write(duration);
227  stream->write(n_loops);
228  stream->write(trigger_mask);
229  stream->writeInt(match_type, 1);
230  stream->writeInt(match_state, 2);
231  stream->writeInt(phrase_type, 1);
232
233  stream->writeFlag(no_choreographer_trigs);
234  stream->writeFlag(no_cons_trigs);
235  stream->writeFlag(no_player_trigs);
236
237  stream->writeString(on_trig_cmd);
238
239  pack_fx(stream, fx_list, mPacked);
240}
241
242void afxPhraseEffectData::unpackData(BitStream* stream)
243{
244  Parent::unpackData(stream);
245
246  stream->read(&duration);
247  stream->read(&n_loops);
248  stream->read(&trigger_mask);
249  match_type = stream->readInt(1);
250  match_state = stream->readInt(2);
251  phrase_type = stream->readInt(1);
252
253  no_choreographer_trigs = stream->readFlag();
254  no_cons_trigs = stream->readFlag();
255  no_player_trigs = stream->readFlag();
256
257  on_trig_cmd = stream->readSTString();
258
259  do_id_convert = true;
260  unpack_fx(stream, fx_list);
261}
262
263bool afxPhraseEffectData::preload(bool server, String &errorStr)
264{
265  if (!Parent::preload(server, errorStr))
266    return false;
267
268  // Resolve objects transmitted from server
269  if (!server) 
270  {
271    if (do_id_convert)
272    {
273      for (S32 i = 0; i < fx_list.size(); i++)
274      {
275        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
276        if (db_id != 0)
277        {
278          // try to convert id to pointer
279          if (!Sim::findObject(db_id, fx_list[i]))
280          {
281            Con::errorf(ConsoleLogEntry::General, 
282              "afxPhraseEffectData::preload() -- bad datablockId: 0x%x", 
283              db_id);
284          }
285        }
286      }
287      do_id_convert = false;
288    }
289  }
290
291  return true;
292}
293
294void afxPhraseEffectData::gather_cons_defs(Vector<afxConstraintDef>& defs)
295{
296  afxConstraintDef::gather_cons_defs(defs, fx_list);
297}
298
299//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
300
301DefineEngineMethod( afxPhraseEffectData, pushEffect, void, ( afxEffectBaseData* effectData ),,
302   "Add a child effect to a phrase effect datablock. Argument can be an afxEffectWrappperData or an afxEffectGroupData.\n" )
303{
304  if (!effectData) 
305  {
306    Con::errorf("afxPhraseEffectData::pushEffect() -- failed to resolve effect datablock.");
307    return;
308  }
309
310  object->fx_list.push_back(effectData);
311}
312
313//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
314