Totem Arts Staff Handepsilon Posted July 28, 2015 Totem Arts Staff Share Posted July 28, 2015 Due to the lack of kismet support, I decided to make some notes about kismeting out buildings. For now I'll do the Action and Event 1. Toggleable Advanced Defense For those who saw and played Coastal, you might be familiar with this one, so we'll take the obelisk as reference So for the Obelisk, we'll do this Placable building actor : class Rx_Building_Obelisk_Toggleable extends Rx_Building_Obelisk placeable; var(Toggling) bool bLaserActivated; //I forgot what this variable is for, maybe it's not needed, but we'll keep it for safety. Feel free to delete this and see what happens though /* The Toggle action kismet node will check for actions that goes like this. Whenever there's this function, the Toggle kismet will always fire it up, if the actor is in the Target variable */ simulated function OnToggle(SeqAct_Toggle action) { /* We'll pass this to the laser shooter actor because through the editor, only the visual building (this actor) is selectable and assignable to the kismet. Just so to be safe, we'll also pass the toggle Kismet Node Parameter*/ Rx_Sentinel_Obelisk_Laser_Toggleable(Rx_Building_Obelisk_Internals_Toggleable(BuildingInternals).laserSentinel).OnToggle(action); } defaultproperties { BuildingInternalsClass = Rx_Building_Obelisk_Internals_Toggleable } class Rx_Building_Obelisk_Internals_Toggleable extends Rx_Building_Obelisk_Internals; Internal actor : /* We rewrite the whole function, and change one line only laserSentinel = Spawn(class'Rx_Sentinel_Obelisk_Laser_Toggleable',,,,,,true); */ function SetupLaser() { local vector v,v2; laserSentinel = Spawn(class'Rx_Sentinel_Obelisk_Laser_Toggleable',,,,,,true); laserSentinel.SetOwner(self); laserSentinel.Team = self.TeamID; if(laserSentinel != none) { laserSentinel.bCollideWorld = true; //Turn off collision and translate, because collision may move the Sentinel away from the ceiling when it's spawned. v = BuildingSkeleton.GetBoneLocation('Ob_Fire'); v.z += 100; laserSentinel.setFireStartLoc(v); v2 = BuildingVisuals.location; v2.z = v.z; v2 = v2 + Normal(v-v2)*100; laserSentinel.setlocation(v2); Rx_Building_Obelisk(BuildingVisuals).SentinelLocation = laserSentinel.location; laserSentinel.Initialize(); CrystalGlowMIC = BuildingSkeleton.CreateAndSetMaterialInstanceConstant(0); Rx_SentinelWeapon_Obelisk(laserSentinel.SWeapon).CrystalGlowMIC = CrystalGlowMIC; laserSentinel.SController.TargetWaitTime = 6.0; laserSentinel.SController.bSeeFriendly=false; laserSentinel.SController.TargetWaitTime=3.0; laserSentinel.SController.SightCounterInterval=0.1; Rx_SentinelWeapon_Obelisk(laserSentinel.SWeapon).InitAndAttachMuzzleFlashes(BuildingSkeleton, 'Ob_Fire'); } } And finally, the 'Sentinel' shooter actor //============================================================================= // Controls the Obi Laser //============================================================================= class Rx_Sentinel_Obelisk_Laser_Toggleable extends Rx_Sentinel_Obelisk_Laser_Base; var bool bLaserDown; //self-explanatory /* This is the function passed from the building InputLinks correspond to the order of the left box in the node, or you can just check in the SeqAct_Toggle scripting */ simulated function OnToggle(SeqAct_Toggle action) { if (action.InputLinks[0].bHasImpulse) { //Turn on bLaserDown = FALSE; } else if (action.InputLinks[1].bHasImpulse) { //Turn off bLaserDown = TRUE; } else if (action.InputLinks[2].bHasImpulse) { // basically toggle the laser bLaserDown = !bLaserDown; } } /* Once again we rewrite this whole script (just copy and paste) but we add our LaserDown variable to check if the obelisk can fire or not */ function bool FireAt(Vector Spot) { local Vector Origin; local bool bFired; Origin = GetPawnViewLocation(); // if(RDiff(DesiredAim, CurrentAimNoRoll) <= SWeapon.GetMaxAimError()) // { if(VSize(Spot - Origin) <= GetRange() && !bLaserDown) { if(SWeapon.FireAt(Origin, CurrentAim, Spot)) { UpgradeManager.NotifyFired(); bForceNetUpdate = true; } bFired = true; } // } return bFired; } Example Kismet : 2. Capturable MCT Event And now we get to the new Beta 5 feature, the new Capturable MCT which.... unfortunately is not fully supported yet IMO. So now we start with actually creating an event for capturing and neutralizing it, just like the Power Node in UT3 but much simpler So now, we start with... Placable building actor: class Rx_CapturableMCT_Kismet extends Rx_CapturableMCT placeable; defaultproperties { BuildingInternalsClass = Rx_CapturableMCT_Internals_Kismet //This is the essential so the actor can be assigned to this event. You can now right click and 'Create event with selected actor' SupportedEvents.Add(class'Rx_SeqEvent_TechCapture') } Internal actor : class Rx_CapturableMCT_Internals_Kismet extends Rx_CapturableMCT_Internals notplaceable; /* We rewrite this because we're going to insert an action. In my opinion it's much easier to tackle this way than using Super.HealDamage() */ function bool HealDamage(int Amount, Controller Healer, class DamageType) { local int RealAmount; local float Scr; if ((Health < HealthMax || Healer.GetTeamNum() != GetTeamNum()) && Amount > 0 && Healer != None ) { RealAmount = Min(Amount, HealthMax - Health); if (RealAmount > 0) { if (Health >= HealthMax && SavedDmg > 0.0f) { SavedDmg = FMax(0.0f, SavedDmg - Amount); Scr = SavedDmg * HealPointsScale; Rx_PRI(Healer.PlayerReplicationInfo).AddScoreToPlayerAndTeam(Scr); } Scr = RealAmount * HealPointsScale; Rx_PRI(Healer.PlayerReplicationInfo).AddScoreToPlayerAndTeam(Scr); } if(Healer.GetTeamNum() != GetTeamNum()) { Amount = -1 * Amount; } Health = Min(HealthMax, Health + Amount); if(Health <= 1) { Health = 1; if(GetTeamNum() != TEAM_NOD && GetTeamNum() != TEAM_GDI) { if(Healer.GetTeamNum() == TEAM_NOD) { `LogRx("GAME"`s "Captured;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); BroadcastLocalizedMessage(MessageClass,NOD_CAPTURED,Healer.PlayerReplicationInfo,,self); ChangeTeamReplicate(TEAM_NOD,true); } else { `LogRx("GAME"`s "Captured;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); BroadcastLocalizedMessage(MessageClass,GDI_CAPTURED,Healer.PlayerReplicationInfo,,self); ChangeTeamReplicate(TEAM_GDI,true); } // TriggerEventClass will trigger out any event node of this type. We set the instigator to healer and set the output to 0. This is the Captured event BuildingVisuals.TriggerEventClass(Class'Rx_SeqEvent_TechCapture',Healer,0); // } else { if (TeamID == TEAM_NOD) BroadcastLocalizedMessage(MessageClass,NOD_LOST,Healer.PlayerReplicationInfo,,self); else if (TeamID == TEAM_GDI) BroadcastLocalizedMessage(MessageClass,GDI_LOST,Healer.PlayerReplicationInfo,,self); `LogRx("GAME"`s "Neutralized;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); ChangeTeamReplicate(255,true); Health = BuildingVisuals.HealthMax; // Another trigger event with input set to 1. This is the Neutralized event BuildingVisuals.TriggerEventClass(Class'Rx_SeqEvent_TechCapture',Healer,1); // } } else if (Amount < 0) TriggerUnderAttack(); // We can actually make another event here for the Under Attack event return True; } return False; } DefaultProperties { TeamID = 255 } The kismet node class Rx_SeqEvent_TechCapture extends SequenceEvent; defaultproperties { ObjName="Tech Building Event" ObjCategory="Renegade X Buildings" // There OutputLinks correspond to the TriggerEventClass function parameter. So if there should be more output links, it can be added at will OutputLinks[0]=(LinkDesc="Captured") OutputLinks[1]=(LinkDesc="Neutralized") bPlayerOnly=false MaxTriggerCount=0 } Example Kismet Node : And there we have it. Feel free to ask anything. PS : Hopefully this is included in the next patch, as well as some more Kismet nodes such as Building Destroyed, Harvester dumped and spawned from refinery (Destroyed is already tackled in the Vehicle script), add score, etc. Quote Link to comment Share on other sites More sharing options...
Ruud033 Posted July 28, 2015 Share Posted July 28, 2015 Awesome coding there dude! Thanks for this. I was looking for something similair but I didnt have the time to code it myself / do some research. Thanks again! The 1 thing I'd like to see in your code is the broadcast being changed. Right now, if we capture an MCT it says: " captured the" without a name. For example, when capturing a silo, it prompts us with: " captured the Silo". Can't we have a string variable (name of the MCT) inside the MCT that will get displayed just like "silo" in the silo capture broadcast? Could you include this in your code? Quote Link to comment Share on other sites More sharing options...
Totem Arts Staff Handepsilon Posted July 28, 2015 Author Totem Arts Staff Share Posted July 28, 2015 Unfortunately.... that's what I'm still wondering about. I believe the 'Silo' part is localized string. I remembered when I had Indo localization, the string got replaced. But the script is still a jumble for me. Whatever it is, it might be hidden starting beneath the HealDamage Quote Link to comment Share on other sites More sharing options...
Totem Arts Staff kenz3001 Posted July 28, 2015 Totem Arts Staff Share Posted July 28, 2015 i believe you can add the name to the cap MCT that adds the player caped xxxxxx but i need to test that out Quote Link to comment Share on other sites More sharing options...
Totem Arts Staff Handepsilon Posted July 28, 2015 Author Totem Arts Staff Share Posted July 28, 2015 Uh well, there's no additional variable on the Capturable MCT, so... if you're talking about properties, no there's none. Also tried to do Rx_BuildingObjective and it doesn't produce any result Quote Link to comment Share on other sites More sharing options...
Ruud033 Posted July 29, 2015 Share Posted July 29, 2015 I think this is the place to search for: Rx_Building_Techbuilding_Internals.uc class Rx_Building_TechBuilding_Internals extends Rx_Building_Team_Internals notplaceable implements(RxIfc_Capturable); // The way the sound arrays are setup did not consider changing ownership. I ain't going to rip out the system and replace it, so I'll just use the shit that's there and be efficent at memory usage seeing as I can't do anything else const GDI_CAPTURED = 1; const NOD_CAPTURED = 2; const GDI_LOST = 3; const NOD_LOST = 4; const GDI_UNDERATTACK = 5; const NOD_UNDERATTACK = 6; `define GdiCapSound FriendlyBuildingSounds[buildingRepaired] `define GdiLostSound FriendlyBuildingSounds[buildingDestroyed] `define NodCapSound EnemyBuildingSounds[buildingRepaired] `define NodLostSound EnemyBuildingSounds[buildingDestroyed] `define GdiUnderAttackForGdiSound FriendlyBuildingSounds[buildingUnderAttack] `define GdiUnderAttackForNodSound FriendlyBuildingSounds[buildingDestructionImminent] `define NodUnderAttackForGdiSound EnemyBuildingSounds[buildingUnderAttack] `define NodUnderAttackForNodSound EnemyBuildingSounds[buildingDestructionImminent] var TEAM ReplicatedTeamID; var repnotify TEAM FlagTeam; var MaterialInstanceConstant MICFlag; var Rx_CapturePoint_TechBuilding CP; var float LastUnderAttackAnnouncement; var float UnderAttackAnnouncementCooldown; replication { if(bNetDirty || bNetInitial) ReplicatedTeamID,FlagTeam,CP; } simulated event ReplicatedEvent(name VarName) { if ( VarName == 'FlagTeam' ) FlagChanged(); else super.ReplicatedEvent(VarName); } simulated function Init(Rx_Building Visuals, bool isDebug ) { super.Init(Visuals, isDebug); //SetupCapturePoint(); MICFlag = BuildingSkeleton.CreateAndSetMaterialInstanceConstant(0); FlagChanged(); } simulated function FlagChanged() { if(FlagTeam == TEAM_NOD) MICFlag.SetScalarParameterValue('FlagTeamNum', 1); else if(FlagTeam == TEAM_GDI) MICFlag.SetScalarParameterValue('FlagTeamNum', 0); else MICFlag.SetScalarParameterValue('FlagTeamNum', 2); } function ChangeFlag(TEAM ToTeam) { FlagTeam = ToTeam; FlagChanged(); } function SetupCapturePoint() { local Vector L; local Rotator R; BuildingSkeleton.GetSocketWorldLocationAndRotation('CapturePoint', L, R); CP = Spawn(class'Rx_CapturePoint_TechBuilding',self,,L,R); } function ChangeTeamReplicate(TEAM ToTeam, optional bool bChangeFlag=false) { TeamID = ToTeam; ReplicatedTeamID = ToTeam; if (bChangeFlag) ChangeFlag(ToTeam); } // damage is ignored, can only be captured through the MCT function TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser); function bool HealDamage(int Amount, Controller Healer, class DamageType) { local int RealAmount; local float Scr; if ((Health < HealthMax || Healer.GetTeamNum() != GetTeamNum()) && Amount > 0 && Healer != None ) { RealAmount = Min(Amount, HealthMax - Health); if (RealAmount > 0) { if (Health >= HealthMax && SavedDmg > 0.0f) { SavedDmg = FMax(0.0f, SavedDmg - Amount); Scr = SavedDmg * HealPointsScale; Rx_PRI(Healer.PlayerReplicationInfo).AddScoreToPlayerAndTeam(Scr); } Scr = RealAmount * HealPointsScale; Rx_PRI(Healer.PlayerReplicationInfo).AddScoreToPlayerAndTeam(Scr); } if(Healer.GetTeamNum() != GetTeamNum()) { Amount = -1 * Amount; } Health = Min(HealthMax, Health + Amount); if(Health <= 1) { Health = 1; if(GetTeamNum() != TEAM_NOD && GetTeamNum() != TEAM_GDI) { if(Healer.GetTeamNum() == TEAM_NOD) { `LogRx("GAME"`s "Captured;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); BroadcastLocalizedMessage(MessageClass,NOD_CAPTURED,Healer.PlayerReplicationInfo,,self); ChangeTeamReplicate(TEAM_NOD,true); } else { `LogRx("GAME"`s "Captured;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); BroadcastLocalizedMessage(MessageClass,GDI_CAPTURED,Healer.PlayerReplicationInfo,,self); ChangeTeamReplicate(TEAM_GDI,true); } } else { if (TeamID == TEAM_NOD) BroadcastLocalizedMessage(MessageClass,NOD_LOST,Healer.PlayerReplicationInfo,,self); else if (TeamID == TEAM_GDI) BroadcastLocalizedMessage(MessageClass,GDI_LOST,Healer.PlayerReplicationInfo,,self); `LogRx("GAME"`s "Neutralized;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s `PlayerLog(Healer.PlayerReplicationInfo) ); ChangeTeamReplicate(255,true); Health = BuildingVisuals.HealthMax; } } else if (Amount < 0) TriggerUnderAttack(); return True; } return False; } function TriggerUnderAttack() { if (WorldInfo.TimeSeconds < LastUnderAttackAnnouncement + UnderAttackAnnouncementCooldown) return; if (TeamID == TEAM_GDI) BroadcastLocalizedMessage(MessageClass,GDI_UNDERATTACK,,,self); else if (TeamID == TEAM_Nod) BroadcastLocalizedMessage(MessageClass,NOD_UNDERATTACK,,,self); LastUnderAttackAnnouncement = WorldInfo.TimeSeconds; } function NotifyBeginCaptureBy(byte TeamIndex) { if (TeamIndex == TEAM_GDI) { ChangeFlag(TEAM_GDI); } else if (TeamIndex == TEAM_Nod) { ChangeFlag(TEAM_Nod); } } function NotifyCapturedBy(byte TeamIndex) { `LogRx("GAME"`s "Captured;" `s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s"by"`s class'Rx_Game'.static.GetTeamName(TeamIndex) ); if (TeamIndex == TEAM_GDI) BroadcastLocalizedMessage(MessageClass,GDI_CAPTURED,,,self); else BroadcastLocalizedMessage(MessageClass,NOD_CAPTURED,,,self); if (TeamIndex == TEAM_GDI) ChangeTeamReplicate(TEAM_GDI); else if (TeamIndex == TEAM_Nod) ChangeTeamReplicate(TEAM_Nod); } function NotifyBeginNeutralizeBy(byte TeamIndex) { TriggerUnderAttack(); } function NotifyNeutralizedBy(byte TeamIndex, byte PreviousOwner) { `LogRx("GAME"`s "Neutralized;"`s class'Rx_Game'.static.GetTeamName(TeamID)$","$self.class `s "id" `s GetRightMost(self) `s "by" `s class'Rx_Game'.static.GetTeamName(TeamIndex) ); if (TeamID == TEAM_GDI) BroadcastLocalizedMessage(MessageClass,GDI_LOST,,,self); else BroadcastLocalizedMessage(MessageClass,NOD_LOST,,,self); ChangeTeamReplicate(255); if (TeamIndex == TEAM_GDI) ChangeFlag(TEAM_GDI); else if (TeamIndex == TEAM_Nod) ChangeFlag(TEAM_Nod); } function NotifyRestoredNeutral() { ChangeFlag(255); } function NotifyRestoredCaptured(); simulated function SoundNodeWave GetAnnouncment(int alarm, int teamNum ) { switch ( alarm ) { case GDI_CAPTURED: if (teamNum == TEAM_GDI) return `GdiCapSound; break; case NOD_CAPTURED: if (teamNum == TEAM_Nod) return `NodCapSound; break; case GDI_LOST: if (teamNum == TEAM_GDI) return `GdiLostSound; break; case NOD_LOST: if (teamNum == TEAM_Nod) return `NodLostSound; break; case GDI_UNDERATTACK: if (teamNum == TEAM_Nod) return `GdiUnderAttackForNodSound; else return `GdiUnderAttackForGDISound; case NOD_UNDERATTACK: if (teamNum == TEAM_Nod) return `NodUnderAttackForNodSound; else return `NodUnderAttackForGDISound; } return None; } static function string GetLocalString( optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2 ) { local string str; if (RelatedPRI_1 != None) { switch (Switch) { case GDI_CAPTURED: case NOD_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", RelatedPRI_1.PlayerName); return Repl(str, "`BuildingName`", default.BuildingName); case GDI_LOST: case NOD_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", RelatedPRI_1.PlayerName); return Repl(str, "`BuildingName`", default.BuildingName); } } else { switch (Switch) { case GDI_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", "GDI"); return Repl(str, "`BuildingName`", default.BuildingName); case NOD_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", "Nod"); return Repl(str, "`BuildingName`", default.BuildingName); case GDI_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", "Nod"); return Repl(str, "`BuildingName`", default.BuildingName); case NOD_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", "GDI"); return Repl(str, "`BuildingName`", default.BuildingName); } } return ""; } DefaultProperties { MessageClass=class'Rx_Message_TechBuilding' `GdiCapSound = SoundNodeWave'RX_EVA_VoiceClips.gdi_eva.S_EVA_GDI_TechBuilding_Captured' `GdiLostSound = SoundNodeWave'RX_EVA_VoiceClips.gdi_eva.S_EVA_GDI_TechBuilding_Lost' `NodCapSound = SoundNodeWave'RX_EVA_VoiceClips.Nod_EVA.S_EVA_Nod_TechBuilding_Captured' `NodLostSound = SoundNodeWave'RX_EVA_VoiceClips.Nod_EVA.S_EVA_Nod_TechBuilding_Lost' `GdiUnderAttackForGdiSound = SoundNodeWave'RX_EVA_VoiceClips.gdi_eva.S_EVA_GDI_GDISilo_UnderAttack' `GdiUnderAttackForNodSound = SoundNodeWave'RX_EVA_VoiceClips.Nod_EVA.S_EVA_Nod_GDISilo_UnderAttack' `NodUnderAttackForGdiSound = SoundNodeWave'RX_EVA_VoiceClips.gdi_eva.S_EVA_GDI_NodSilo_UnderAttack' `NodUnderAttackForNodSound = SoundNodeWave'RX_EVA_VoiceClips.Nod_EVA.S_EVA_Nod_NodSilo_UnderAttack' //AttachmentClasses.Remove(Rx_BuildingAttachment_MCT) //AttachmentClasses.Add(Rx_BuildingAttachment_MCT_TechBuilding) ReplicatedTeamID=255 FlagTeam=255 UnderAttackAnnouncementCooldown = 15 } and then this specific piece of code: static function string GetLocalString( optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2 ) { local string str; if (RelatedPRI_1 != None) { switch (Switch) { case GDI_CAPTURED: case NOD_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", RelatedPRI_1.PlayerName); return Repl(str, "`BuildingName`", default.BuildingName); case GDI_LOST: case NOD_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", RelatedPRI_1.PlayerName); return Repl(str, "`BuildingName`", default.BuildingName); } } else { switch (Switch) { case GDI_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", "GDI"); return Repl(str, "`BuildingName`", default.BuildingName); case NOD_CAPTURED: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[2], "`PlayerName`", "Nod"); return Repl(str, "`BuildingName`", default.BuildingName); case GDI_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", "Nod"); return Repl(str, "`BuildingName`", default.BuildingName); case NOD_LOST: str = Repl(class'Rx_Message_Buildings'.default.BuildingBroadcastMessages[3], "`PlayerName`", "GDI"); return Repl(str, "`BuildingName`", default.BuildingName); } } return ""; } Quote Link to comment Share on other sites More sharing options...
Totem Arts Staff Handepsilon Posted July 29, 2015 Author Totem Arts Staff Share Posted July 29, 2015 Building Name huh? That variable is not revealed Need to override that part of function then Quote Link to comment Share on other sites More sharing options...
Ruud033 Posted August 22, 2015 Share Posted August 22, 2015 Any news on this?? Quote Link to comment Share on other sites More sharing options...
Totem Arts Staff Handepsilon Posted August 22, 2015 Author Totem Arts Staff Share Posted August 22, 2015 Nah, nothing yet... Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.