From f7ad2607100b97a328e1213425f3fe27754dc079 Mon Sep 17 00:00:00 2001 From: DarkLotus Date: Fri, 12 Apr 2019 15:37:02 +1000 Subject: [PATCH] Initial commit of CEasyUO. --- .gitignore | 2 + App.config | 6 + CEasyUO.csproj | 170 ++ CEasyUOMainForm.Designer.cs | 290 ++++ CEasyUOMainForm.cs | 404 +++++ CEasyUOMainForm.resx | 743 +++++++++ Core/ActionQueue.cs | 661 ++++++++ Core/Actions.cs | 2692 ++++++++++++++++++++++++++++++ Core/Actions.resx | 42 + Core/BuffsDebuffs.cs | 145 ++ Core/BuffsTimer.cs | 44 + Core/Config.cs | 28 + Core/ContainerLabels.cs | 69 + Core/Geometry.cs | 570 +++++++ Core/Item.cs | 817 +++++++++ Core/ItemID.cs | 100 ++ Core/Language.cs | 34 + Core/LocString.cs | 7 + Core/Map.cs | 148 ++ Core/Mobile.cs | 602 +++++++ Core/ObjectPropertyList.cs | 403 +++++ Core/OverheadMessages.cs | 58 + Core/Player.cs | 955 +++++++++++ Core/Serial.cs | 134 ++ Core/SkillTimer.cs | 59 + Core/Spells.cs | 393 +++++ Core/Targeting.cs | 1705 +++++++++++++++++++ Core/Timer.cs | 352 ++++ Core/UOEntity.cs | 140 ++ Core/Utility.cs | 382 +++++ Core/World.cs | 121 ++ Engine.cs | 83 + Network/ClientCommunication.cs | 196 +++ Network/Handlers.cs | 2458 +++++++++++++++++++++++++++ Network/Packet.cs | 2428 +++++++++++++++++++++++++++ Network/PacketHandler.cs | 220 +++ Network/PacketTable.cs | 408 +++++ Network/Packets.cs | 1624 ++++++++++++++++++ Network/ZLib.cs | 659 ++++++++ Program.cs | 35 + Properties/AssemblyInfo.cs | 36 + Properties/Resources.Designer.cs | 71 + Properties/Resources.resx | 117 ++ Properties/Settings.Designer.cs | 30 + Properties/Settings.settings | 7 + Scripting/AST.cs | 700 ++++++++ Scripting/EUOInterpreter.cs | 565 +++++++ Scripting/EUOParser.cs | 925 ++++++++++ Scripting/EUOVars.cs | 54 + Scripting/Lexer.cs | 303 ++++ Scripting/ParseException.cs | 25 + Scripting/SpracheParser.cs | 37 + Ultima.dll | Bin 0 -> 163840 bytes cuoapi.dll | Bin 0 -> 17920 bytes icons/clinew.ico | Bin 0 -> 318 bytes icons/cliswap.ico | Bin 0 -> 318 bytes icons/close.ico | Bin 0 -> 318 bytes icons/copy.ico | Bin 0 -> 318 bytes icons/cut.ico | Bin 0 -> 318 bytes icons/easyuo2.ico | Bin 0 -> 2238 bytes icons/find.ico | Bin 0 -> 318 bytes icons/help.ico | Bin 0 -> 318 bytes icons/home.ico | Bin 0 -> 318 bytes icons/new.ico | Bin 0 -> 318 bytes icons/open.ico | Bin 0 -> 318 bytes icons/openeuo.ico | Bin 0 -> 4286 bytes icons/paste.ico | Bin 0 -> 318 bytes icons/pause.ico | Bin 0 -> 318 bytes icons/reopen.ico | Bin 0 -> 318 bytes icons/replace.ico | Bin 0 -> 318 bytes icons/save.ico | Bin 0 -> 318 bytes icons/start.ico | Bin 0 -> 318 bytes icons/stop.ico | Bin 0 -> 318 bytes icons/stopall.ico | Bin 0 -> 318 bytes zlib.dll | Bin 0 -> 165944 bytes 75 files changed, 23257 insertions(+) create mode 100644 .gitignore create mode 100644 App.config create mode 100644 CEasyUO.csproj create mode 100644 CEasyUOMainForm.Designer.cs create mode 100644 CEasyUOMainForm.cs create mode 100644 CEasyUOMainForm.resx create mode 100644 Core/ActionQueue.cs create mode 100644 Core/Actions.cs create mode 100644 Core/Actions.resx create mode 100644 Core/BuffsDebuffs.cs create mode 100644 Core/BuffsTimer.cs create mode 100644 Core/Config.cs create mode 100644 Core/ContainerLabels.cs create mode 100644 Core/Geometry.cs create mode 100644 Core/Item.cs create mode 100644 Core/ItemID.cs create mode 100644 Core/Language.cs create mode 100644 Core/LocString.cs create mode 100644 Core/Map.cs create mode 100644 Core/Mobile.cs create mode 100644 Core/ObjectPropertyList.cs create mode 100644 Core/OverheadMessages.cs create mode 100644 Core/Player.cs create mode 100644 Core/Serial.cs create mode 100644 Core/SkillTimer.cs create mode 100644 Core/Spells.cs create mode 100644 Core/Targeting.cs create mode 100644 Core/Timer.cs create mode 100644 Core/UOEntity.cs create mode 100644 Core/Utility.cs create mode 100644 Core/World.cs create mode 100644 Engine.cs create mode 100644 Network/ClientCommunication.cs create mode 100644 Network/Handlers.cs create mode 100644 Network/Packet.cs create mode 100644 Network/PacketHandler.cs create mode 100644 Network/PacketTable.cs create mode 100644 Network/Packets.cs create mode 100644 Network/ZLib.cs create mode 100644 Program.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 Properties/Resources.Designer.cs create mode 100644 Properties/Resources.resx create mode 100644 Properties/Settings.Designer.cs create mode 100644 Properties/Settings.settings create mode 100644 Scripting/AST.cs create mode 100644 Scripting/EUOInterpreter.cs create mode 100644 Scripting/EUOParser.cs create mode 100644 Scripting/EUOVars.cs create mode 100644 Scripting/Lexer.cs create mode 100644 Scripting/ParseException.cs create mode 100644 Scripting/SpracheParser.cs create mode 100644 Ultima.dll create mode 100644 cuoapi.dll create mode 100644 icons/clinew.ico create mode 100644 icons/cliswap.ico create mode 100644 icons/close.ico create mode 100644 icons/copy.ico create mode 100644 icons/cut.ico create mode 100644 icons/easyuo2.ico create mode 100644 icons/find.ico create mode 100644 icons/help.ico create mode 100644 icons/home.ico create mode 100644 icons/new.ico create mode 100644 icons/open.ico create mode 100644 icons/openeuo.ico create mode 100644 icons/paste.ico create mode 100644 icons/pause.ico create mode 100644 icons/reopen.ico create mode 100644 icons/replace.ico create mode 100644 icons/save.ico create mode 100644 icons/start.ico create mode 100644 icons/stop.ico create mode 100644 icons/stopall.ico create mode 100644 zlib.dll diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ded7c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/obj/ diff --git a/App.config b/App.config new file mode 100644 index 0000000..4bba09a --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CEasyUO.csproj b/CEasyUO.csproj new file mode 100644 index 0000000..5e9613a --- /dev/null +++ b/CEasyUO.csproj @@ -0,0 +1,170 @@ + + + + + Debug + AnyCPU + {003E3EEF-9EDE-4188-B513-EDDA5722147B} + WinExe + CEasyUO + CEasyUO + v4.7.1 + 512 + true + false + + + x64 + true + full + false + Z:\480gb\ClassicUO\bin\Debug\Data\Plugins\CEasyUO\ + DEBUG;TRACE + prompt + 4 + true + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + icons\easyuo2.ico + + + + False + .\cuoapi.dll + + + C:\Users\James\Documents\Visual Studio 2017\Projects\EasyLoA\EasyAntlr\bin\Debug\EasyAntlr.exe + + + + + + + + + + + + + + False + .\Ultima.dll + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + CEasyUOMainForm.cs + + + + + + + + + + + Actions.cs + + + CEasyUOMainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/CEasyUOMainForm.Designer.cs b/CEasyUOMainForm.Designer.cs new file mode 100644 index 0000000..0c2b7ec --- /dev/null +++ b/CEasyUOMainForm.Designer.cs @@ -0,0 +1,290 @@ +namespace CEasyUO +{ + partial class CEasyUOMainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose( bool disposing ) + { + if ( disposing && ( components != null ) ) + { + components.Dispose(); + } + base.Dispose( disposing ); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CEasyUOMainForm)); + this.treeVarTree = new System.Windows.Forms.TreeView(); + this.txtScriptEntry = new System.Windows.Forms.RichTextBox(); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.btnNew = new System.Windows.Forms.ToolStripButton(); + this.btnOpen = new System.Windows.Forms.ToolStripButton(); + this.btnSave = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.btnPlay = new System.Windows.Forms.ToolStripButton(); + this.btnPause = new System.Windows.Forms.ToolStripButton(); + this.btnStop = new System.Windows.Forms.ToolStripButton(); + this.btnStep = new System.Windows.Forms.ToolStripButton(); + this.btnCompile = new System.Windows.Forms.ToolStripButton(); + this.txtDebug = new System.Windows.Forms.Label(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.tree_AST = new System.Windows.Forms.TreeView(); + this.toolStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + this.SuspendLayout(); + // + // treeVarTree + // + this.treeVarTree.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.treeVarTree.Location = new System.Drawing.Point(0, 3); + this.treeVarTree.Name = "treeVarTree"; + this.treeVarTree.Size = new System.Drawing.Size(212, 381); + this.treeVarTree.TabIndex = 0; + // + // txtScriptEntry + // + this.txtScriptEntry.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtScriptEntry.Location = new System.Drawing.Point(25, 3); + this.txtScriptEntry.MaxLength = 327670; + this.txtScriptEntry.Name = "txtScriptEntry"; + this.txtScriptEntry.ShowSelectionMargin = true; + this.txtScriptEntry.Size = new System.Drawing.Size(683, 578); + this.txtScriptEntry.TabIndex = 1; + this.txtScriptEntry.Text = resources.GetString("txtScriptEntry.Text"); + // + // toolStrip1 + // + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.btnNew, + this.btnOpen, + this.btnSave, + this.toolStripSeparator1, + this.btnPlay, + this.btnPause, + this.btnStop, + this.btnStep, + this.btnCompile}); + this.toolStrip1.Location = new System.Drawing.Point(0, 0); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Size = new System.Drawing.Size(957, 25); + this.toolStrip1.TabIndex = 2; + this.toolStrip1.Text = "toolStrip1"; + // + // btnNew + // + this.btnNew.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnNew.Image = ((System.Drawing.Image)(resources.GetObject("btnNew.Image"))); + this.btnNew.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnNew.Name = "btnNew"; + this.btnNew.Size = new System.Drawing.Size(23, 22); + this.btnNew.Text = "New"; + this.btnNew.Click += new System.EventHandler(this.btnNew_Click); + // + // btnOpen + // + this.btnOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnOpen.Image = ((System.Drawing.Image)(resources.GetObject("btnOpen.Image"))); + this.btnOpen.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnOpen.Name = "btnOpen"; + this.btnOpen.Size = new System.Drawing.Size(23, 22); + this.btnOpen.Text = "Open"; + this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click); + // + // btnSave + // + this.btnSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnSave.Image = ((System.Drawing.Image)(resources.GetObject("btnSave.Image"))); + this.btnSave.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(23, 22); + this.btnSave.Text = "Save"; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + // + // btnPlay + // + this.btnPlay.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnPlay.Image = ((System.Drawing.Image)(resources.GetObject("btnPlay.Image"))); + this.btnPlay.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnPlay.Name = "btnPlay"; + this.btnPlay.Size = new System.Drawing.Size(23, 22); + this.btnPlay.Text = "Play"; + this.btnPlay.Click += new System.EventHandler(this.btnPlayClicked); + // + // btnPause + // + this.btnPause.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnPause.Image = ((System.Drawing.Image)(resources.GetObject("btnPause.Image"))); + this.btnPause.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnPause.Name = "btnPause"; + this.btnPause.Size = new System.Drawing.Size(23, 22); + this.btnPause.Text = "Pause"; + this.btnPause.Click += new System.EventHandler(this.btnPauseClicked); + // + // btnStop + // + this.btnStop.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnStop.Image = ((System.Drawing.Image)(resources.GetObject("btnStop.Image"))); + this.btnStop.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnStop.Name = "btnStop"; + this.btnStop.Size = new System.Drawing.Size(23, 22); + this.btnStop.Text = "Stop"; + this.btnStop.Click += new System.EventHandler(this.btnStopClicked); + // + // btnStep + // + this.btnStep.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnStep.Image = ((System.Drawing.Image)(resources.GetObject("btnStep.Image"))); + this.btnStep.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnStep.Name = "btnStep"; + this.btnStep.Size = new System.Drawing.Size(23, 22); + this.btnStep.Text = "Step"; + this.btnStep.Click += new System.EventHandler(this.btnStepClicked); + // + // btnCompile + // + this.btnCompile.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.btnCompile.Image = ((System.Drawing.Image)(resources.GetObject("btnCompile.Image"))); + this.btnCompile.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btnCompile.Name = "btnCompile"; + this.btnCompile.Size = new System.Drawing.Size(23, 22); + this.btnCompile.Text = "Check"; + this.btnCompile.Click += new System.EventHandler(this.btnCompile_Click); + // + // txtDebug + // + this.txtDebug.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.txtDebug.AutoSize = true; + this.txtDebug.Location = new System.Drawing.Point(12, 615); + this.txtDebug.Name = "txtDebug"; + this.txtDebug.Size = new System.Drawing.Size(35, 13); + this.txtDebug.TabIndex = 3; + this.txtDebug.Text = "label1"; + // + // splitContainer1 + // + this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer1.Location = new System.Drawing.Point(12, 28); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.txtScriptEntry); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.splitContainer2); + this.splitContainer1.Size = new System.Drawing.Size(933, 584); + this.splitContainer1.SplitterDistance = 711; + this.splitContainer1.TabIndex = 4; + // + // splitContainer2 + // + this.splitContainer2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer2.Location = new System.Drawing.Point(3, 3); + this.splitContainer2.Name = "splitContainer2"; + this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.treeVarTree); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.tree_AST); + this.splitContainer2.Size = new System.Drawing.Size(215, 581); + this.splitContainer2.SplitterDistance = 387; + this.splitContainer2.TabIndex = 1; + // + // tree_AST + // + this.tree_AST.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tree_AST.Location = new System.Drawing.Point(0, 3); + this.tree_AST.Name = "tree_AST"; + this.tree_AST.Size = new System.Drawing.Size(212, 184); + this.tree_AST.TabIndex = 0; + // + // CEasyUOMainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(957, 637); + this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.txtDebug); + this.Controls.Add(this.toolStrip1); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "CEasyUOMainForm"; + this.Text = "CEasyUO 0.2"; + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TreeView treeVarTree; + private System.Windows.Forms.RichTextBox txtScriptEntry; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripButton btnPlay; + private System.Windows.Forms.Label txtDebug; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.ToolStripButton btnPause; + private System.Windows.Forms.ToolStripButton btnStop; + private System.Windows.Forms.ToolStripButton btnStep; + private System.Windows.Forms.ToolStripButton btnNew; + private System.Windows.Forms.ToolStripButton btnOpen; + private System.Windows.Forms.ToolStripButton btnSave; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.TreeView tree_AST; + private System.Windows.Forms.ToolStripButton btnCompile; + } +} + diff --git a/CEasyUOMainForm.cs b/CEasyUOMainForm.cs new file mode 100644 index 0000000..315a256 --- /dev/null +++ b/CEasyUOMainForm.cs @@ -0,0 +1,404 @@ +using Assistant; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace CEasyUO +{ + public partial class CEasyUOMainForm : Form + { + public CEasyUOMainForm() + { + + InitializeComponent(); + this.Text = $"CEasyUO {Assembly.GetExecutingAssembly().GetName().Version}"; + SetupVarsTimer(); + btnPause.Enabled = false; + btnStop.Enabled = false; + } + + private void SetupVarsTimer() + { + treeVarTree.Nodes.Add( new TreeNode( "Character Info" ) ); + treeVarTree.Nodes.Add( new TreeNode( "Status Bar" ) ); + treeVarTree.Nodes.Add( new TreeNode( "Container Info" ) ); + treeVarTree.Nodes.Add( new TreeNode( "Last Action" ) ); + treeVarTree.Nodes.Add( new TreeNode( "Find Item" ) ); + treeVarTree.Nodes.Add( new TreeNode( "Extended Info" ) ); + Thread t = new Thread( new ThreadStart( () => { + while ( true ) + { + Thread.Sleep( 1000 ); + if(this.IsHandleCreated) + if ( InvokeRequired ) + { + BeginInvoke( new MethodInvoker( UpdateVars ) ); + } + else + { + UpdateVars(); + // Do things + } + } + + + + } ) ); + t.IsBackground = true; + t.Start(); + + } + + private void UpdateVars() + { + if(Interpreter != null && Interpreter.CurrentStatment != null) + { + + } + try + { + //txtDebug.Text = "CurrentLine: " + Parser?.CurrentLine ?? "0"; + if ( !Engine.IsInstalled || World.Player == null ) + { + return; + } + + Dictionary charinfo = BuildCharInfo(); + + + Dictionary last = BuildLastInfo(); + + Dictionary container = new Dictionary(); + container.Add( "#GUMPPOSX", EUOInterpreter.GetVariable( "#GUMPPOSX" ) ); + container.Add( "#GUMPPOSY", EUOInterpreter.GetVariable( "#GUMPPOSY" ) ); + container.Add( "#GUMPSIZEX", EUOInterpreter.GetVariable( "#GUMPSIZEX" ) ); + container.Add( "#GUMPSIZEY", EUOInterpreter.GetVariable( "#GUMPSIZEY" ) ); + container.Add( "#CONTKIND", EUOInterpreter.GetVariable( "#CONTKIND" ) ); + container.Add( "#CONTID", EUOInterpreter.GetVariable( "#CONTID" ) ); + container.Add( "#CONTTYPE", EUOInterpreter.GetVariable( "#CONTTYPE" ) ); + container.Add( "#CONTHP", "N/A" ); + + container.Add( "#GUMPSERIAL", EUOInterpreter.GetVariable( "#GUMPSERIAL" ) ); + container.Add( "#GUMPTYPE", EUOInterpreter.GetVariable( "#GUMPTYPE" ) ); + + Dictionary find = new Dictionary(); + find.Add( "#FINDID", EUOInterpreter.GetVariable( "#findid" ) ); + find.Add( "#FINDTYPE", EUOInterpreter.GetVariable( "#FINDTYPE" ) ); + find.Add( "#FINDX", EUOInterpreter.GetVariable( "#FINDX" ) ); + find.Add( "#FINDY", EUOInterpreter.GetVariable( "#FINDY" ) ); + find.Add( "#FINDZ", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDDIST", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDKIND", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDSTACK", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDBAGID", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDMOD", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDREP", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDCOL", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDINDEX", EUOInterpreter.GetVariable( "#FINDZ" ) ); + find.Add( "#FINDCNT", EUOInterpreter.GetVariable( "#FINDZ" ) ); + + + Dictionary status = new Dictionary(); + status.Add( "#CHARNAME", EUOInterpreter.GetVariable( "#CHARNAME" ) ); + status.Add( "#SEX", EUOInterpreter.GetVariable( "#SEX" ) ); + status.Add( "#STR", EUOInterpreter.GetVariable( "#STR" ) ); + status.Add( "#DEX", EUOInterpreter.GetVariable( "#DEX" ) ); + status.Add( "#INT", EUOInterpreter.GetVariable( "#INT" ) ); + status.Add( "#HITS", EUOInterpreter.GetVariable( "#HITS" ) ); + status.Add( "#MAXHITS", EUOInterpreter.GetVariable( "#MAXHITS" ) ); + status.Add( "#MANA", EUOInterpreter.GetVariable( "#MANA" ) ); + status.Add( "#MAXMANA", EUOInterpreter.GetVariable( "#MAXMANA" ) ); + status.Add( "#STAMINA", EUOInterpreter.GetVariable( "#STAMINA" ) ); + status.Add( "#MAXSTAM", EUOInterpreter.GetVariable( "#MAXSTAM" ) ); + status.Add( "#MAXSTATS", EUOInterpreter.GetVariable( "#MAXSTATS" ) ); + status.Add( "#LUCK", EUOInterpreter.GetVariable( "#LUCK" ) ); + status.Add( "#WEIGHT", EUOInterpreter.GetVariable( "#WEIGHT" ) ); + status.Add( "#MAXWEIGHT", EUOInterpreter.GetVariable( "#MAXWEIGHT" ) ); + status.Add( "#MINDMG", EUOInterpreter.GetVariable( "#MINDMG" ) ); + status.Add( "#MAXDMG", EUOInterpreter.GetVariable( "#MAXDMG" ) ); + status.Add( "#GOLD", EUOInterpreter.GetVariable( "#GOLD" ) ); + status.Add( "#FOLLOWERS", EUOInterpreter.GetVariable( "#FOLLOWERS" ) ); + status.Add( "#MAXFOL", EUOInterpreter.GetVariable( "#MAXFOL" ) ); + status.Add( "#AR", EUOInterpreter.GetVariable( "#AR" ) ); + status.Add( "#FR", EUOInterpreter.GetVariable( "#FR" ) ); + status.Add( "#CR", EUOInterpreter.GetVariable( "#CR" ) ); + status.Add( "#PR", EUOInterpreter.GetVariable( "#PR" ) ); + status.Add( "#ER", EUOInterpreter.GetVariable( "#ER" ) ); + + Dictionary extended = new Dictionary(); + extended.Add( "#JOURNAL", EUOInterpreter.GetVariable( "#JOURNAL" ) ); + extended.Add( "#JCOLOR", EUOInterpreter.GetVariable( "#JCOLOR" ) ); + extended.Add( "#JINDEX", EUOInterpreter.GetVariable( "#JINDEX" ) ); + extended.Add( "#SYSMSG", EUOInterpreter.GetVariable( "#SYSMSG" ) ); + extended.Add( "#TARGCURS", EUOInterpreter.GetVariable( "#TARGCURS" ) ); + extended.Add( "#CURSKIND", EUOInterpreter.GetVariable( "#CURSKIND" ) ); + extended.Add( "#PROPERTY", EUOInterpreter.GetVariable( "#PROPERTY" ) ); + + Dictionary results = new Dictionary(); + results.Add( "#RESULT", EUOInterpreter.GetVariable( "#RESULT" ) ); + results.Add( "#STRRES", EUOInterpreter.GetVariable( "#STRRES" ) ); + results.Add( "#MENURES", EUOInterpreter.GetVariable( "#MENURES" ) ); + results.Add( "#DISPRES", EUOInterpreter.GetVariable( "#DISPRES" ) ); + + foreach ( TreeNode n in treeVarTree.Nodes ) + { + if ( n.Text == "Last Action" ) + UpdateChildren( (TreeNode)n, last ); + if ( n.Text == "Character Info" ) + UpdateChildren( (TreeNode)n, charinfo ); + if ( n.Text == "Find Item" ) + UpdateChildren( (TreeNode)n, find ); + if ( n.Text == "Status Bar" ) + UpdateChildren( (TreeNode)n, status ); + if ( n.Text == "Container Info" ) + UpdateChildren( (TreeNode)n, container ); + if ( n.Text == "Result Variables" ) + UpdateChildren( (TreeNode)n, results ); + if ( n.Text == "Extended Info" ) + UpdateChildren( (TreeNode)n, extended ); + } + } + catch ( Exception e ) + { + + Console.WriteLine( e.Message + e.StackTrace ); + } + } + + private Dictionary BuildLastInfo() + { + var last = new Dictionary(); + last.Add( "#LOBJECTID", EUOInterpreter.GetVariable( "#LOBJECTID" ) ); + last.Add( "#LOBJECTTYPE", EUOInterpreter.GetVariable( "#LOBJECTTYPE" ) ); + last.Add( "#LTARGETID", EUOInterpreter.GetVariable( "#LTARGETID" ) ); + last.Add( "#LTARGETX", EUOInterpreter.GetVariable( "#LTARGETX" ) ); + last.Add( "#LTARGETY", EUOInterpreter.GetVariable( "#LTARGETY" ) ); + last.Add( "#LTARGETZ", EUOInterpreter.GetVariable( "#LTARGETZ" ) ); + last.Add( "#LTARGETKIND", EUOInterpreter.GetVariable( "#LTARGETKIND" ) ); + last.Add( "#LTARGETTILE", EUOInterpreter.GetVariable( "#LTARGETTILE" ) ); + last.Add( "#LSKILL", EUOInterpreter.GetVariable( "#LSKILL" ) ); + last.Add( "#LSPELL", EUOInterpreter.GetVariable( "#LSPELL" ) ); + + last.Add( "#LGUMPBUTTON", EUOInterpreter.GetVariable( "#LGUMPBUTTON" ) ); + + return last; + } + + private Dictionary BuildCharInfo() + { + var charinfo = new Dictionary(); + charinfo.Add( "#CHARPOSX", EUOInterpreter.GetVariable( "#CHARPOSX" ) ); + charinfo.Add( "#CHARPOSY", EUOInterpreter.GetVariable( "#CHARPOSY" ) ); + charinfo.Add( "#CHARPOSZ", EUOInterpreter.GetVariable( "#CHARPOSZ" ) ); + charinfo.Add( "#CHARDIR", EUOInterpreter.GetVariable( "#CHARDIR" ) ); + charinfo.Add( "#CHARSTATUS", EUOInterpreter.GetVariable( "#CHARSTATUS" ) ); ; + charinfo.Add( "#CHARID", EUOInterpreter.GetVariable( "#CHARID" ) ); + charinfo.Add( "#CHARTYPE", EUOInterpreter.GetVariable( "#CHARTYPE" ) ); + charinfo.Add( "#CHARGHOST", EUOInterpreter.GetVariable( "#CHARGHOST" ) ); + charinfo.Add( "#CHARBACKPACKID", EUOInterpreter.GetVariable( "#CHARBACKPACKID" ) ); + return charinfo; + } + + private void UpdateChildren( TreeNode n, Dictionary dict ) + { try + { + foreach ( var c in dict ) + { + if ( n.Nodes.ContainsKey( c.Key ) ) + n.Nodes[c.Key].Text = c.Key + ": " + c.Value.ToString(); + else + n.Nodes.Add( c.Key, c.Key + ": " + c.Value.ToString() ); + } + } + catch (Exception e) + { + Debugger.Break(); + Console.WriteLine( e.Message + e.StackTrace ); + } + + } + + public EUOInterpreter Interpreter; + private FileStream m_OpenFile; + + public string m_FilePath { get; private set; } + + private void btnPlayClicked( object sender, EventArgs e ) + { + if ( Interpreter == null || Interpreter.Script != txtScriptEntry.Text ) + { + Interpreter = new EUOInterpreter( txtScriptEntry.Text ); + UpdateAST(); + } + + if(Interpreter.Running && Interpreter.Paused) + { + Interpreter.Paused = false; + } + else if ( !Interpreter.Running ) + { + Interpreter.Run(); + } + + btnPlay.Enabled = false; + btnStop.Enabled = true; + btnPause.Enabled = true; + txtDebug.Text = "Running..."; + + } + + private void btnPauseClicked( object sender, EventArgs e ) + { + if ( Interpreter == null ) + return; + if ( !Interpreter.Running ) + return; + + Interpreter.Paused = true; + + btnPlay.Enabled = true; + btnStop.Enabled = true; + btnPause.Enabled = false; + txtDebug.Text = "Paused on Line: " + Interpreter.CurrentLine; + } + + private void btnStopClicked( object sender, EventArgs e ) + { + if ( Interpreter == null ) + return; + if ( Interpreter.Running ) + Interpreter.Stop(); + btnPlay.Enabled = true; + btnStop.Enabled = false; + btnPause.Enabled = false; + txtDebug.Text = "Stopped..."; + } + + private void btnStepClicked( object sender, EventArgs e ) + { + try + { + if ( Interpreter == null || Interpreter.Script != txtScriptEntry.Text ) + { + Interpreter = new EUOInterpreter( txtScriptEntry.Text ); + UpdateAST(); + } + + + Interpreter.Statement(); + txtDebug.Text = "Current Line: " + Interpreter.CurrentLine + " Current Statement: " + Interpreter.CurrentStatment?? "null"; + var start = txtScriptEntry.GetFirstCharIndexFromLine( Interpreter.CurrentLine -1 ); + var end = txtScriptEntry.GetFirstCharIndexFromLine( Interpreter.CurrentLine ) - 1; + + txtScriptEntry.SelectionStart = 0; + txtScriptEntry.SelectionLength = txtScriptEntry.Text.Length; + txtScriptEntry.SelectionBackColor = SystemColors.Window; + + + txtScriptEntry.SelectionStart = start; + txtScriptEntry.SelectionLength = end - start; + txtScriptEntry.SelectionBackColor = Color.Red; + txtScriptEntry.SelectionBullet = true; + txtScriptEntry.SelectionLength = 0; + + } catch(Exception ee ) + { + txtDebug.Text = "E: " + ee.Message; + } + + + } + + private void btnNew_Click( object sender, EventArgs e ) + { + txtScriptEntry.Text = ""; + if(m_OpenFile != null) + { + m_OpenFile.Close(); + m_OpenFile = null; + } + m_FilePath = null; + } + + private void btnOpen_Click( object sender, EventArgs e ) + { + var diag = new OpenFileDialog(); + if(diag.ShowDialog() == DialogResult.OK ) + { + m_OpenFile = File.Open( diag.FileName, FileMode.OpenOrCreate ); + m_FilePath = diag.FileName; + using(var sr = new StreamReader( m_OpenFile ) ) + txtScriptEntry.Text = sr.ReadToEnd(); + } + } + + private void btnSave_Click( object sender, EventArgs e ) + { + if ( m_FilePath != null ) + { + m_OpenFile = File.Open( m_FilePath, FileMode.Truncate ); + using ( var sw = new StreamWriter( m_OpenFile ) ) + { + sw.Write( txtScriptEntry.Text ); + //sw.Flush(); + } + + } + else + { + var diag = new SaveFileDialog(); + if ( diag.ShowDialog() == DialogResult.OK ) + { + m_OpenFile = File.Open( diag.FileName, FileMode.OpenOrCreate ); + m_FilePath = diag.FileName; + using(var sw = new StreamWriter( m_OpenFile ) ) + { + sw.Write( txtScriptEntry.Text ); + //sw.Flush(); + } + + } + } + + } + + private void btnCompile_Click( object sender, EventArgs e ) + { + UpdateVars(); + UpdateAST(); + } + + public void UpdateAST() + { + if ( Interpreter == null || Interpreter.Script != txtScriptEntry.Text ) + Interpreter = new EUOInterpreter( txtScriptEntry.Text ); + tree_AST.Nodes.Clear(); + + foreach ( var n in ( Interpreter.AST.First() as Block ).statements ) + tree_AST.Nodes.AddRange( AddTree( n ) ); + } + + private TreeNode[] AddTree( Stmt n ) + { + + var node = new TreeNode( n.ToString() ); + if ( n is Block b ) + { + foreach(var s in b.statements) + node.Nodes.AddRange( AddTree( s ) ); + } + return new TreeNode[] { node }; + + } + } +} diff --git a/CEasyUOMainForm.resx b/CEasyUOMainForm.resx new file mode 100644 index 0000000..d590e4c --- /dev/null +++ b/CEasyUOMainForm.resx @@ -0,0 +1,743 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ; Configuration +if ( #charName = Night ) +{ + set %runebookId TJQDKMD ; Runebook ID + set %dropContainerId XWNAKMD + set %lumberjackSpots 8 ; Number of spots in runebook +} +if ( #charName = Dragono ) +{ + set %runebookId TSNFKMD ; Runebook ID + set %dropContainerId BCCWJMD + set %lumberjackSpots 6 ; Number of spots in runebook +} +set %area 40 ; Area to scan +set %pathfindTimeout 10 ; Maximum time allowed per pathfind (seconds) +set %maximumPathfindDistance 17 ; Maximum distance to pathFind +set %smartPathFindTimeout 60 ; Maximum time allowed per smart pathfind (seconds) +set %rechargeRunebook #true ; Recharge runebook (#true/#false) +set %maxRunebookCharges 10 ; Maximum charges in runebook +set %toolsToCraft 10 ; When out of hatchets, amount to craft +set %toolsRecharge 5 ; Amount of tools to have before going back lumberjacking (1 if you got newbied hatchet) +set %deathTime 2400 ; Time to wait before resurrecting (20 = 1 second) + +; Constants +set %tools FSF_ASF +set %tinkeringTools GTL +set %recallScrolls WTL +set %treasureBalls DMF + +; Stats +set %recallScrollsUsed 0 +set %toolsUsed 0 + +; Variables +set %currentLumberjackSpot 1 +set %ironRequiredTool 4 +set %bankingRequired #false + +tile init + +lumberjacking: +set %bankingRequired #false +gosub resetSpots +for %currentSpot 1 %spots +{ + set %spotX %spotX . %currentSpot + set %spotY %spotY . %currentSpot + set %spotZ %spotZ . %currentSpot + set %spotTileType %spotTileType . %currentSpot + + gosub smartPathFind %spotX %spotY %spotZ 1 + gosub lumberjack %spotX %spotY %spotZ %spotTileType + + if ( #charGhost = yes ) + { + gosub resurrect + break + } + + if ( %bankingRequired = #true ) + break +} +set %currentLumberjackSpot %currentLumberjackSpot + 1 +if ( %currentLumberjackSpot > %lumberjackSpots ) + set %currentLumberjackSpot 1 +gosub bank +goto lumberjacking + +sub resetSpots +linesPerCycle 1000 +set %_startX #charPosX - %area +set %_endX #charPosX + %area +set %_startY #charPosY - %area +set %_endY #charPosY + %area +set %spots 0 +for %_resetSpotsLoopX %_startX %_endX +{ + for %_resetSpotsLoopY %_startY %_endY + { + tile cnt %_resetSpotsLoopX %_resetSpotsLoopY + + for %_resetSpotsLoopTile 1 #tileCnt + { + tile get %_resetSpotsLoopX %_resetSpotsLoopY %_resetSpotsLoopTile + + if ( #tiletype >= 3277 && #tiletype <= 3302 ) + { + set %spots %spots + 1 + set %spotX . %spots %_resetSpotsLoopX + set %spotY . %spots %_resetSpotsLoopY + set %spotZ . %spots #tileZ + set %spotTileType . %spots #tileType + break + } + } + } +} +linesPerCycle 10 +return + +; gosub smartPathFind [x] [y] [z] [tolerance] +sub smartPathFind +linesPerCycle 1000 +set %_smartPathFindX %1 +set %_smartPathFindY %2 +set %_smartPathFindZ %3 +set %_smartPathFindTolerance %4 +set %_smartPathFindTimeout #scnt + %smartPathFindTimeout +gosub isCharPosWithinTolerance %_smartPathFindX %_smartPathFindY %maximumPathfindDistance +while ( #result = #false ) +{ + if ( %_smartPathFindTimeout < #scnt ) + break + + set %_bestCandidateX #charPosX + set %_bestCandidateY #charPosY + set %_bestCandidateZ #charPosZ + + for %_candidate 1 %spots + { + set %_candidateX %spotX . %_candidate + set %_candidateY %spotY . %_candidate + set %_candidateZ %spotZ . %_candidate + + gosub isBetterSmartPathFindCandidate %_candidateX %_candidateY %_smartPathFindX %_smartPathFindY %_bestCandidateX %_bestCandidateY %maximumPathfindDistance + if ( #result = #true ) + { + set %_bestCandidateX %_candidateX + set %_bestCandidateY %_candidateY + set %_bestCandidateZ %_candidateZ + } + } + + if ( %_bestCandidateX = #charPosX && %_bestCandidateY = #charPosY ) + goto _smartPathFindReturnFalse ; no candidate found + + gosub pathFind %_bestCandidateX %_bestCandidateY %_bestCandidateZ %_smartPathFindTolerance + if ( #result = #false ) + goto _smartPathFindReturnFalse + gosub isCharPosWithinTolerance %_smartPathFindX %_smartPathFindY %maximumPathfindDistance +} +linesPerCycle 10 +gosub pathFind %_smartPathFindX %_smartPathFindY %_smartPathFindZ %_smartPathFindTolerance %_smartPathFindTimeout +return #result +_smartPathFindReturnFalse: +linesPerCycle 10 +return #false + +; gosub isBetterSmartPathFindCandidate candidateX candidateY destinationX destinationY bestCandidateX bestCandidateY maxDistance +sub isBetterSmartPathFindCandidate +set %_distanceToCandidateX #charPosX - %1 abs +set %_distanceToCandidateY #charPosY - %2 abs + +if ( %_distanceToCandidateX > %7 || %_distanceToCandidateY > %7 ) + return #false ; Candidate is out of reach + +set %_distanceCandidateToDestinationX %1 - %3 abs +set %_distanceCandidateToDestinationY %2 - %4 abs +set %_distanceCandidateToDestination %_distanceCandidateToDestinationX + %_distanceCandidateToDestinationY + +set %_distanceBestCandidateToDestinationX %5 - %3 abs +set %_distanceBestCandidateToDestinationY %6 - %4 abs +set %_distanceBestCandidateToDestination %_distanceBestCandidateToDestinationX + %_distanceBestCandidateToDestinationY + +if ( %_distanceCandidateToDestination < %_distanceBestCandidateToDestination ) + return #true ; Candidate is in reach and nearer to wanted destination + +return #false ; Candidate is in reach, but farer to wanted destination + +; gosub pathfind [x] [y] [z] [tolerance] +sub pathfind +set %_pathFindX %1 +set %_pathFindY %2 +set %_pathFindZ %3 +set %_pathFindTolerance %4 +set %_pathfindMoved #false +set %_timeout #scnt + %pathfindTimeout +set %_journal #jIndex + 1 +gosub isCharPosWithinTolerance %_pathFindX %_pathFindY %_pathFindTolerance +if ( #result = #true ) +{ + if ( %_pathfindMoved = #true ) + event pathfind 0 0 0 + return #true +} +event pathfind %_pathFindX %_pathFindY %_pathFindZ +_pathfind: +gosub isCharPosWithinTolerance %_pathFindX %_pathFindY %_pathFindTolerance +if ( #result = #true ) +{ + if ( %_pathfindMoved = #true ) + event pathfind 0 0 0 + return #true +} +if ( %_timeout < #scnt ) +{ + if ( %_pathfindMoved = #true ) + event pathfind 0 0 0 + return #false +} +if ( %_journal <= #jIndex ) +{ + scanJournal %_journal + if ( get_there in #journal ) + return #false + if ( pathfinding in #journal ) + set %_pathfindMoved #true + set %_journal %_journal + 1 +} +goto _pathfind + +sub isCharPosWithinTolerance +set %_x %1 +set %_y %2 +set %_dist %3 +set %_distX #charPosX - %_x abs +set %_distY #charPosY - %_y abs +if ( %_distX <= %_dist && %_distY <= %_dist ) + return #true +return #false + +sub lumberjack +set %_lumberjackX %1 +set %_lumberjackY %2 +set %_lumberjackZ %3 +set %_lumberjackTileType %4 +_lumberjack: +findItem %tools C_ +if ( #findKind = -1 ) +{ + set %bankingRequired #true + return +} +set #lObjectId #findId +set #lTargetX %_lumberjackX +set #lTargetY %_lumberjackY +set #lTargetZ %_lumberjackZ +set #lTargetTile %_lumberjackTileType +set #lTargetKind 3 +set %_lumberjackJournal #jIndex +event macro 17 +target +event macro 22 +set %_lumberjackTimeout #scnt + 10 +_lumberjackJournal: +if ( %_lumberjackTimeout < #scnt ) + return +if ( #jIndex <= %_journal ) + goto _lumberjackJournal +set %_journal %_journal + 1 +scanJournal %_journal +if ( you_hack_at_the_tree in #journal ) + goto _lumberjack +if ( you_put in #journal ) + goto _lumberjack +if ( there_is_nothing in #journal ) + return +if ( it_appears_immune in #journal ) + return +if ( reach_this in #journal ) + return +goto _lumberjackJournal + +sub bank +gosub recall 1 +gosub dropLogs +gosub dropTreasureBalls +gosub restockTools +if ( %rechargeRunebook = #true ) + gosub rechargeRunebook +set %_currentLumberjackSpot %currentLumberjackSpot + 1 +gosub recall %_currentLumberjackSpot +return + +sub restockTools +ignoreItem reset +_restockTools: +findItem %tools C_ , #backPackId +if ( #findCnt >= %toolsRecharge ) + return +set %toolsToGrab %toolsRecharge - #findCnt +gosub openContainer %dropContainerId +findItem %tools C_ , %dropContainerId +if ( #findCnt < %toolsToGrab ) +{ + gosub craftTools + gosub dropLogs ; safety + goto _restockTools +} +for %_loop 1 %toolsToGrab +{ + findItem %tools C_ , %dropContainerId + exevent drag #findId + exevent dropC #backPackId + wait 10 +} +set %toolsUsed %toolsUsed + %toolsToGrab +return + +sub craftTools +ignoreItem reset +findItem %tinkeringTools C_ , %dropContainerId +if ( #findKind = -1 ) + pause +exevent drag #findId 1 +exevent dropC #backPackId +wait 10 +_findIronIngots: +findItem RMK C_ , %dropContainerId +if ( #findKind = -1 ) + pause +if ( #findCol <> 0 ) +{ + ignoreItem #findId + goto _findIronIngots +} +set %_ironAmount %ironRequiredTool * %toolsToCraft +exevent drag #findId %_ironAmount +exevent dropC #backPackId +wait 10 +ignoreItem reset +_craftTool: +findItem RMK C_ , #backPackId +if ( #findCol <> 0 ) +{ + ignoreItem #findId + goto _craftTool +} +if ( #findKind = -1 || #findStack < %ironRequiredTool ) + goto _dropTinkeringTools +msg $,waitmenu 'Tinkering' 'Tools' 'Tools' 'hatchet'$ +findItem %tinkeringTools C_ , #backPackId +set #lObjectId #findId +event macro 17 +wait 80 +goto _craftTool +_dropTinkeringTools: +ignoreItem reset +findItem %tinkeringTools C_ , #backPackId +exevent drag #findId 1 +exevent dropC %dropContainerId +wait 10 +_dropTools: +findItem %tools C_ , #backPackId +if ( #findKind = -1 ) + return +exevent drag #findId 1 +exevent dropC %dropContainerId 50 50 +wait 10 +goto _dropTools + +; gosub recall [rune] +sub recall +set %_rune %1 +if ( %_rune <= 8 ) +{ + set %_runeX 85 + set %_runeY 115 + ( ( %_rune - 1 ) * 14 ) +} +else +{ + set %_runeX 245 + set %_runeY 115 + ( ( %_rune - 9 ) * 14 ) +} +set %_charPosX #charPosX +set %_charPosY #charPosY +_openRunebook: +set %_openRunebook #scnt + 10 +set #lObjectId %runebookId +event macro 17 +_waitOpenRunebook: +if ( %_openRunebook < #scnt ) + goto _openRunebook +if ( #contSizeX <> 352 || #contSizeY <> 226 ) + goto _waitOpenRunebook +set %_recall #scnt + 10 +click %_runeX %_runeY +_waitRecall: +if ( %_recall < #scnt ) + goto _openRunebook +if ( #charPosX <> %_charPosX || #charPosY <> %_charPosY ) + return +goto _waitRecall + +sub rechargeRunebook +ignoreItem reset +gosub openContainer %dropContainerId +_rechargeRunebook: +findItem %recallScrolls C_ , %dropContainerId +if ( #findKind = -1 ) + pause ; Out of recall scrolls +if ( #findCol <> 0 ) +{ + ignoreItem #findId + goto rechargeRunebook +} +exevent drag #findId %maxRunebookCharges +exevent dropC %runebookId +wait 10 +findItem %recallScrolls C_ , #backPackId +set %recallScrollsUsed %recallScrollsUsed + ( %maxRunebookCharges - #findStack ) +exevent drag #findId #findStack +exevent dropC %dropContainerId +wait 10 +return + +; gosub openContainer [containerId] +sub openContainer +set %_containerId %1 +_openContainer: +set #lObjectId %_containerId +event macro 17 +set %_timeout #scnt + 10 +_waitOpenContainer: +if ( %_timeout < #scnt ) + goto _openContainer +if ( #contId = %_containerId ) + return +goto _openContainer + +sub dropLogs +ignoreItem reset +_dropLogs: +findItem ZLK_RMK C_ , #backPackId +if ( #findKind = -1 ) + return +exevent drag #findId #findStack +exevent dropC %dropContainerId +wait 10 +goto _dropLogs + +sub dropTreasureBalls +ignoreItem reset +_dropTreasureBalls: +findItem %treasureBalls C_ , #backPackId +if ( #findKind = -1 ) + return +exevent drag #findId 1 +exevent dropC %dropContainerId +wait 10 +goto _dropTreasureBalls + +sub resurrect +set %_resurrectX #charPosX +set %_resurrectY #charPosY +_resurrect: +if ( #charGhost <> yes ) + return +msg $home home home$ +wait 10 +if ( #charPosX = %_resurrectX || #charPosY = %_resurrectY ) + goto _resurrect +wait %deathTime +gosub pathfind 5182 1250 0 1 +gosub pathfind 5182 1237 0 1 +gosub pathfind 5182 1228 10 0 +gosub pathfind 5182 1223 40 1 +move 5182 1223 1 30s +ignoreItem reset +_clickResurrectionStone: +findItem HTG G_1 +if ( #findKind = -1 ) + pause +if ( #findCol <> 66 ) +{ + ignoreItem #findId + goto _clickResurrectionStone +} +ignoreItem reset +set #lObjectId #findId +event macro 17 +gosub waitGump 380 150 +if ( #result = #false ) + goto _clickResurrectionStone +click 72 100 +wait 120 +if ( #charGhost = yes ) + goto _clickResurrectionStone +event macro 8 1 ; open paperdoll +event macro 8 2 ; open status +event macro 8 7 ; open backpack +wait 20 +move 5194 1229 0 10s +return + +; gosub waitGump width height +sub waitGump +set %_waitGumpWidth %1 +set %_waitGumpHeight %2 +set %_waitGumpTimeout #scnt + 5 +_waitGump: +if ( %_waitGumpTimeout < #scnt ) + return #false +if ( #contSizeX = %_waitGumpWidth && #contSizeY = %_waitGumpHeight ) + return #true +goto _waitGump + + + + 17, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABfSURBVDhPzcoxDgAhEELRuf+lNQg2i6KFxf5kMgWvXtYO + d6ztwqaLiXtzwx9y00CrsH1umXgOjtwTycGReyI5OHJPJAdH7onk4Mg9kRwcuSeSgyP3RHJw5N4cb+43 + VXWxN5h2GtXL+gAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABdSURBVDhPzY4BCsAgDMR8uj/vCHJlruLUwljgKOqlWL7C + btnmKel8tEyE3+hiZWvomhmB7nGUIS7PJr1Wj3hpNum1esRLmtxtBLGXa32PZEjJkJIhJYMeVvI7SrkA + G1/vNRaHbsAAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABQSURBVDhP3YxJCgAgDAP7dH+uhJIggkvVgzgQLGaxW+RN + iZySf85gBi+KJDRQSYQGmEWR/DJQGV01OSFjVeh41WmNobYGCO43B4DCAZ1iVgBP6Iyo8QiMcwAAAABJ + RU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABNSURBVDhPrc47DgAgCERB7n8qboYEpdBo+KyvIRazkf4n + JMws61XPMDTiuDuy4dbIiasjV+w31Qv7jQpw4hcQ1iBsQViDsAXhGYSLEQ3oEK4fx9qumgAAAABJRU5E + rkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABuSURBVDhPrY1BCsAwDMP69Py8o4uz2GT4VEEwVIKui8SO + iH02j3EOVKCbOEfoLxo5ByrQTZwjOpiRc6AC3cQ5ooMZOQcq0E2cIzqYkXOgAt3EOaKDGTkHKtBNnCM6 + mJFzHx3MyDniCD48v/y7tR6j8SJ+dmnfjQAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAB8SURBVDhPjY1BEsAgCAN5uj+34JgaEZSd2UMlSSWiifTI + ec75w026pjfnADzJil4aWlTL8BixBw683AbCv4Pke3TUMZL+3UO32oAJ3HttwEO39wAIvlG+D1w8Bozq + SFg28MhhlotQa+YiCmVqfC8T4xiVTNwt+ILDrEPkA426iyckmvhvAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG + YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 + 0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw + bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc + VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 + c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 + Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo + mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ + kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG + YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 + 0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw + bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc + VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 + c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 + Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo + mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ + kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + + + AAABAAEAICAIAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACAAAAAAAAAQAAAAAAAAAAAAAAAEAAAAB + AAD/////iPkSAEibg3wIJoB8/////wAmgHxCJYB8mAAAACrakXzHJIB8mAAAAAAAAACY+RIAeldtdJgA + AAAAX210DDBudLC3FQCgAgkAhAHRAAAA0QCgAgkADDBudGT5EgB0+RIAmAAAAJgAAAAIX210/////wBf + bXTv92t08A8AAMbAAAACAAAAf/hrdBipAACo+xIAeFWDANj5EgAmizZ+eFWDAOz///8AAAAAqPsSADBK + NgAAAAAAAAAAAADw/X9E+hIAKiaAfAD6EgAAJoB8zPoSAAAAAAAAAAAAFAAAAAEAAAAAAAAAAAAAABAA + AACADwX9/////wCA/X8A8P1/FPoSAAAAAAD0+RIAAAAAAJT6EgBIm4N8CCaAfP////8AJoB8QiWAfJgA + AAAq2pF8xySAfJgAAAAAAAAApPoSAHpXbXSYAAAAKl1tdMz6EgAAAAAAAAAAAAQA0QDGwAAAwAAAALB3 + N373dzd+nAEAAMbAAAAAAAAAAAAAANz6EgAq2pF8xySAfIAAAAAAAAAAZPsSAFFZanRWWWp08A8AAICK + Nn4AAAAAnAEAAAAAAAAAAAAAAAAAAAAAAADICzwAAAA8AMALPAAAADwAAAA8AAAAPAAAADwAoE29AKCc + xQDwDwAA6gIEAAAAPABE+xIAAAA8AFABAAAAADwAAAA8APYCBAAAAAAAAAAAAPAPAAAAAAAAXDBudEqA + TgABAAAAAgAAAAUAAADA+hIAbPsSALT7EgCm8W10MBdqdP////9WWWp0gPsSAAG0N36wA4AABAAAAAQA + CgAAAAAAxPsSADETa3SXAAIABAAAAAYDBgA2E2t0BAAAAAQACgAAAAAAAAA8AJcAAgCY+xIAAAAAACT8 + EgCm8W10QBNrdP////82E2t0Ixk4fgQAAAAGAwYAAAAAAAAAAAB4Y0wAzPwSAADw/X8AAAAA6gIEAAQA + AAABAAAANPwSABezN34EAAoABgMGAAAAAAAmszd+AAAAAHhjTADM/BIAEPwSALj8EgC4/BIAjwQ5fjCz + N37/////EFTIAPlGQAA8VMgAf3NEAAQAAAAQVMgA/XBEABBUyAAUckQAAAAAAPSGwQDsqUMAsPwSAHlt + RAAAAAAAAAAAALD8EgAAAAAAEFTIAOypQwAdbUQANCtOAHCYvwBjf04AuPwSAHh/TgCAf04ANzZAAAIA + AAA8/RIApkZAAG5uQAA0K04AcJi/AKKFTgCqhU4ABgMGAHCYvwBYAAAABgMGAAAAQAB8AbcAAAAAAAAA + AABE/RIAikZAAE9tQAAEAAAAmm9AAET9EgAEAAAAOwAAAN1+QAD0hsEAOAAAAEz9EgAAAAAAAAAAAKDJ + yACOe0cAm5ubm5ubm5ubm5ubm5ubA5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmwNWA5ubm5ub + m5ubm5ubm5ubm5ubm5ubm5ubm5ubmwAAYgNi/v6bm5ubm5ubm5ubm5ubm5ubm5ubm5ubmwD+JWJiA2Ji + JSUAm5ubm5ubm5ubm5ubm5ubm5ubm5sAJWJimwMDm5tiYiUAm5ubm5ubm5ubm5ubm5ubm5ubACVimwMD + A1YDAwMPYiUAm5ubm5ubm5ubm5ubm5ubmwAlYpsDm5ubA5ubmwObYiUAm5ubm5ubm5ubm5ubm5slJSWb + A5ubm5ubm5ubmwObYiUlm5ubm5ubm5ubm5ubm/4lYpubm5ubm5ubm5ubm5tiJf6bm5ubm5ubm5ubm5ub + /mKbA5ubm5ubm5ubm5ubA5ti/ptWVlabm5ubm5ubmwNiYpsDm5ubm5ubm5ubm5sDm2JiA5tWVpubm5ub + m5ubVgMDA1YDm5ubm5ubm5ubA1YDAwNWm1ZWVpubVlZWVlZWVlabA5tWVlabVlabm1ZWVlZiYgObVlZW + m5tWVlZWVlZWVpsDVlZWVlZWm5tWVlZWVlYlm5tWVlabm1ZWm5ubm/4lYptWVpubVlabm5ubm5tWVv6b + VlabVlabVlabm5ubmwBimwNWVptWVpubm1ZWVlZWm5tWVptWVptWVpubm5ub/iVimwNWVlZWm5tWVlZW + Vv6bm1ZWm1ZWm1ZWVlZWVlZW/iVimwMDVlZiA1ZWYiX+m5ubVlabVlabVlZWVlZWVlab/lZWVlZWVgNi + VlZWVlZWm1ZWm5ubVlZWVpubm5ubm5ubm1ZWVlZim1ZiVlZWVpubVlabm5tWVlZWm5ubm5ubm5sl/mhi + A5sDJWIAJZubm5ubm5ubm5ubVlZWVlZWVlYA/v4laCWbVpslaGL+AACbm5ubm5ubm5tWVlZWVlZWVv4l + aCWbm5ubm5ubJWIl/gCbm5ubm5ubm5ubm5ubm/7+JWglm5ubm5ubm5ubJWIl/gCbm5ubm5ubm5ubm5v+ + ACViJZubm5ubm5ubm5ubJWIl/v6bm5ubm5ubm5ubm5tiYpubm5ubm5ubm5ubm5ubm2Jim5ubm5ubm5ub + m5ubAwMDA5ubm5ubm5ubm5ubm5sDAwMDm5ubm5ubm5ubm5ubJWKbm5ubm5ubm5ubm5ubm5tiYpubm5ub + m5ubm5ubm5sAJWKbm5ubm5ubm5ubm5ubYiX+m5ubm5ubm5ubm5ubm5v+JWKbm5ubm5ubm5ubm2IlJZub + m5ubm5ubm5ubm5ub/gD+/v6bm5ubm5ubm5sA/v7+/pubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ub + m5ubm5ubm5ubm//8f///8B///8AH//+AA///AAH//gAA//wAAH/4DuA/+B/wP/gf8AfwH/AH4A/gAwAC + YAMAAMAjOAzwCTwE4Ek8AABJAAAAyQAAAJw/gAGcPwAB/wAAAP8AAoB/+A/gP/Af8B/wP/gf8D/4H/A/ + +B/4P/g//B/wf/gP4D/8H/B/ + + + \ No newline at end of file diff --git a/Core/ActionQueue.cs b/Core/ActionQueue.cs new file mode 100644 index 0000000..d2142a3 --- /dev/null +++ b/Core/ActionQueue.cs @@ -0,0 +1,661 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Assistant +{ + public delegate void DropDoneCallback( Serial iser, Serial dser, Point3D newPos ); + + public class DragDropManager + { + public enum ProcStatus + { + Nothing, + Success, + KeepWaiting, + ReQueue + } + + public static bool Debug = false; + + private static void Log( string str, params object[] args ) + { + if ( Debug ) + { + try + { + using ( StreamWriter w = new StreamWriter( "DragDrop.log", true ) ) + { + w.Write( ":: " ); + w.WriteLine( str, args ); + w.Flush(); + } + } + catch + { + } + } + } + + private class LiftReq + { + private static int NextID = 1; + + public LiftReq( Serial s, int a, bool cli, bool last ) + { + Serial = s; + Amount = a; + FromClient = cli; + DoLast = last; + Id = NextID++; + } + + public readonly Serial Serial; + public readonly int Amount; + public readonly int Id; + public readonly bool FromClient; + public readonly bool DoLast; + + public override string ToString() + { + return String.Format( "{2}({0},{1},{3},{4})", Serial, Amount, Id, FromClient, DoLast ); + } + + } + + private class DropReq + { + public DropReq( Serial s, Point3D pt ) + { + Serial = s; Point = pt; + } + + public DropReq( Serial s, Layer layer ) + { + Serial = s; Layer = layer; + } + + public Serial Serial; + public readonly Point3D Point; + public readonly Layer Layer; + } + + public static void Initialize() + { + try { File.Delete( "DragDrop.log" ); } catch {} + } + + private static void DropCurrent() + { + Log( "Drop current requested on {0}", m_Holding ); + + if ( m_Holding.IsItem ) + { + if ( World.Player.Backpack != null ) + ClientCommunication.SendToServer( new DropRequest( m_Holding, Point3D.MinusOne, World.Player.Backpack.Serial ) ); + else + ClientCommunication.SendToServer( new DropRequest( m_Holding, World.Player.Position, Serial.Zero ) ); + } + + Clear(); + } + + private static int m_LastID; + + private static Serial m_Pending, m_Holding; + private static Item m_HoldingItem; + private static bool m_ClientLiftReq = false; + private static DateTime m_Lifted = DateTime.MinValue; + + private static readonly Dictionary> m_DropReqs = new Dictionary>(); + + private static readonly LiftReq[] m_LiftReqs = new LiftReq[ 256 ]; + private static byte m_Front, m_Back; + + public static Item Holding { get { return m_HoldingItem; } } + public static Serial Pending { get { return m_Pending; } } + + public static int LastIDLifted { get { return m_LastID; } } + + public static void Clear() + { + Log( "Clearing...." ); + + m_DropReqs.Clear(); + for(int i=0;i<256;i++) + m_LiftReqs[i] = null; + m_Front = m_Back = 0; + m_Holding = m_Pending = Serial.Zero; + m_HoldingItem = null; + m_Lifted = DateTime.MinValue; + } + + public static void DragDrop( Item i, Serial to ) + { + Drag( i, i.Amount ); + Drop( i, to, Point3D.MinusOne ); + } + + public static void DragDrop( Item i, Item to ) + { + Drag( i, i.Amount ); + Drop( i, to.Serial, Point3D.MinusOne ); + } + + public static void DragDrop( Item i, Point3D dest ) + { + Drag( i, i.Amount ); + Drop( i, Serial.MinusOne, dest ); + } + + public static void DragDrop( Item i, int amount, Item to ) + { + Drag( i, amount ); + Drop( i, to.Serial, Point3D.MinusOne ); + } + + public static void DragDrop( Item i, Mobile to, Layer layer, bool doLast ) + { + Drag( i, i.Amount, false, doLast ); + Drop( i, to, layer ); + } + + public static void DragDrop( Item i, Mobile to, Layer layer ) + { + Drag( i, i.Amount, false ); + Drop( i, to, layer ); + } + + public static int Drag( Item i, int amount, bool fromClient ) + { + return Drag( i, amount, fromClient, false ); + } + + public static int Drag( Item i, int amount ) + { + return Drag( i, amount, false, false ); + } + + public static bool Empty { get { return m_Back == m_Front; } } + public static bool Full { get { return ((byte)(m_Back+1)) == m_Front; } } + + public static int Drag( Item i, int amount, bool fromClient, bool doLast ) + { + LiftReq lr = new LiftReq( i.Serial, amount, fromClient, doLast ); + LiftReq prev = null; + + if ( Full ) + { + if ( fromClient ) + ClientCommunication.SendToClient( new LiftRej() ); + return 0; + } + + Log( "Queuing Drag request {0}", lr ); + + if ( m_Back >= m_LiftReqs.Length ) + m_Back = 0; + + if ( m_Back <= 0 ) + prev = m_LiftReqs[m_LiftReqs.Length-1]; + else if ( m_Back <= m_LiftReqs.Length ) + prev = m_LiftReqs[m_Back-1]; + + // if the current last req must stay last, then insert this one in its place + if ( prev != null && prev.DoLast ) + { + Log( "Back-Queuing {0}", prev ); + if ( m_Back <= 0 ) + m_LiftReqs[m_LiftReqs.Length-1] = lr; + else if ( m_Back <= m_LiftReqs.Length ) + m_LiftReqs[m_Back-1] = lr; + + // and then re-insert it at the end + lr = prev; + } + + m_LiftReqs[m_Back++] = lr; + + ActionQueue.SignalLift( !fromClient ); + return lr.Id; + } + + public static bool Drop( Item i, Mobile to, Layer layer ) + { + if ( m_Pending == i.Serial ) + { + Log( "Equipping {0} to {1} (@{2})", i, to.Serial, layer ); + ClientCommunication.SendToServer( new EquipRequest( i.Serial, to, layer ) ); + m_Pending = Serial.Zero; + m_Lifted = DateTime.MinValue; + return true; + } + else + { + bool add = false; + + for(byte j=m_Front;j!=m_Back && !add;j++) + { + if ( m_LiftReqs[j] != null && m_LiftReqs[j].Serial == i.Serial ) + { + add = true; + break; + } + } + + if ( add ) + { + Log( "Queuing Equip {0} to {1} (@{2})", i, to.Serial, layer ); + + if (!m_DropReqs.TryGetValue(i.Serial, out var q) || q == null) + m_DropReqs[i.Serial] = q = new Queue(); + + q.Enqueue( new DropReq( to == null ? Serial.Zero : to.Serial, layer ) ); + return true; + } + else + { + Log( "Drop/Equip for {0} (to {1} (@{2})) not found, skipped", i, to == null ? Serial.Zero : to.Serial, layer ); + return false; + } + } + } + + public static bool Drop( Item i, Serial dest, Point3D pt ) + { + if ( m_Pending == i.Serial ) + { + Log( "Dropping {0} to {1} (@{2})", i, dest, pt ); + + ClientCommunication.SendToServer( new DropRequest( i.Serial, pt, dest ) ); + m_Pending = Serial.Zero; + m_Lifted = DateTime.MinValue; + return true; + } + else + { + bool add = false; + + for(byte j=m_Front;j!=m_Back && !add;j++) + { + if ( m_LiftReqs[j] != null && m_LiftReqs[j].Serial == i.Serial ) + { + add = true; + break; + } + } + + if ( add ) + { + Log( "Queuing Drop {0} (to {1} (@{2}))", i, dest, pt ); + + if (!m_DropReqs.TryGetValue(i.Serial, out var q) || q == null) + m_DropReqs[i.Serial] = q = new Queue(); + + q.Enqueue( new DropReq( dest, pt ) ); + return true; + } + else + { + Log( "Drop for {0} (to {1} (@{2})) not found, skipped", i, dest, pt ); + return false; + } + } + } + + public static bool Drop( Item i, Item to, Point3D pt ) + { + return Drop( i, to == null ? Serial.MinusOne : to.Serial, pt ); + } + + public static bool Drop( Item i, Item to ) + { + return Drop( i, to.Serial, Point3D.MinusOne ); + } + + public static bool LiftReject() + { + Log( "Server rejected lift for item {0}", m_Holding ); + if ( m_Holding == Serial.Zero ) + return true; + + m_Holding = m_Pending = Serial.Zero; + m_HoldingItem = null; + m_Lifted = DateTime.MinValue; + + return m_ClientLiftReq; + } + + public static bool HasDragFor( Serial s ) + { + for(byte j=m_Front;j!=m_Back;j++) + { + if ( m_LiftReqs[j] != null && m_LiftReqs[j].Serial == s ) + return true; + } + + return false; + } + + public static bool CancelDragFor( Serial s ) + { + if ( Empty ) + return false; + + int skip = 0; + for(byte j=m_Front;j!=m_Back;j++) + { + if ( skip == 0 && m_LiftReqs[j] != null && m_LiftReqs[j].Serial == s ) + { + m_LiftReqs[j] = null; + skip++; + if ( j == m_Front ) + { + m_Front++; + break; + } + else + { + m_Back--; + } + } + + if ( skip > 0 ) + m_LiftReqs[j] = m_LiftReqs[(byte)(j+skip)]; + } + + if ( skip > 0 ) + { + m_LiftReqs[m_Back] = null; + return true; + } + else + { + return false; + } + } + + public static bool EndHolding( Serial s ) + { + //if ( m_Pending == s ) + // return false; + + if ( m_Holding == s ) + { + m_Holding = Serial.Zero; + m_HoldingItem = null; + } + + return true; + } + + private static DropReq DequeueDropFor( Serial s ) + { + DropReq dr = null; + if (m_DropReqs.TryGetValue(s, out var q) && q != null) + { + if ( q.Count > 0 ) + dr = q.Dequeue(); + if ( q.Count <= 0 ) + m_DropReqs.Remove( s ); + } + return dr; + } + + public static void GracefulStop() + { + m_Front = m_Back = 0; + + if ( m_Pending.IsValid ) + { + m_DropReqs.TryGetValue(m_Pending, out var q); + m_DropReqs.Clear(); + m_DropReqs[m_Pending] = q; + } + } + + public static ProcStatus ProcessNext( int numPending ) + { + if ( m_Pending != Serial.Zero ) + { + if ( m_Lifted + TimeSpan.FromMinutes( 2 ) < DateTime.UtcNow ) + { + Item i = World.FindItem( m_Pending ); + + Log( "Lift timeout, forced drop to pack for {0}", m_Pending ); + + if ( World.Player != null ) + { + if ( World.Player.Backpack != null ) + ClientCommunication.SendToServer( new DropRequest( m_Pending, Point3D.MinusOne, World.Player.Backpack.Serial ) ); + else + ClientCommunication.SendToServer( new DropRequest( m_Pending, World.Player.Position, Serial.Zero ) ); + } + + m_Holding = m_Pending = Serial.Zero; + m_HoldingItem = null; + m_Lifted = DateTime.MinValue; + } + else + { + return ProcStatus.KeepWaiting; + } + } + + if ( m_Front == m_Back ) + { + m_Front = m_Back = 0; + return ProcStatus.Nothing; + } + + LiftReq lr = m_LiftReqs[m_Front]; + + if ( numPending > 0 && lr != null && lr.DoLast ) + return ProcStatus.ReQueue; + + m_LiftReqs[m_Front] = null; + m_Front++; + if ( lr != null ) + { + Log( "Lifting {0}", lr ); + + Item item = World.FindItem( lr.Serial ); + if ( item != null && item.Container == null ) + { // if the item is on the ground and out of range then dont grab it + if ( Utility.Distance( item.GetWorldPosition(), World.Player.Position ) > 3 ) + { + Log( "Item is too far away... uncaching." ); + return ProcStatus.Nothing; + } + } + + ClientCommunication.SendToServer( new LiftRequest( lr.Serial, lr.Amount ) ); + + m_LastID = lr.Id; + m_Holding = lr.Serial; + m_HoldingItem = World.FindItem( lr.Serial ); + m_ClientLiftReq = lr.FromClient; + + DropReq dr = DequeueDropFor( lr.Serial ); + if ( dr != null ) + { + m_Pending = Serial.Zero; + m_Lifted = DateTime.MinValue; + + Log( "Dropping {0} to {1}", lr, dr.Serial ); + + if ( dr.Serial.IsMobile && dr.Layer > Layer.Invalid && dr.Layer <= Layer.LastUserValid ) + ClientCommunication.SendToServer( new EquipRequest( lr.Serial, dr.Serial, dr.Layer ) ); + else + ClientCommunication.SendToServer( new DropRequest( lr.Serial, dr.Point, dr.Serial ) ); + } + else + { + m_Pending = lr.Serial; + m_Lifted = DateTime.UtcNow; + } + + return ProcStatus.Success; + } + else + { + Log( "No lift to be done?!" ); + return ProcStatus.Nothing; + } + } + } + + public class ActionQueue + { + private static Serial m_Last = Serial.Zero; + private static readonly Queue m_Queue = new Queue(); + private static readonly ProcTimer m_Timer = new ProcTimer(); + private static int m_Total = 0; + + public static void DoubleClick( bool silent, Serial s ) + { + if ( s != Serial.Zero ) + { + if ( m_Last != s ) + { + m_Queue.Enqueue( s ); + m_Last = s; + m_Total++; + if ( m_Queue.Count == 1 && !m_Timer.Running ) + m_Timer.StartMe(); + + } + + } + } + + public static void SignalLift( bool silent ) + { + m_Queue.Enqueue( Serial.Zero ); + m_Total++; + if ( /*m_Queue.Count == 1 &&*/ !m_Timer.Running ) + m_Timer.StartMe(); + + } + + public static void Stop() + { + if ( m_Timer != null && m_Timer.Running ) + m_Timer.Stop(); + m_Queue.Clear(); + DragDropManager.Clear(); + } + + public static bool Empty { get { return m_Queue.Count <= 0 && !m_Timer.Running; } } + + public static string TimeLeft + { + get + { + if ( m_Timer.Running ) + { + //Config.GetBool("ObjectDelayEnabled") + //double time = Config.GetInt( "ObjectDelay" ) / 1000.0; + + double time = Config.GetInt("ObjectDelay") / 1000.0; + + if (!Config.GetBool("ObjectDelayEnabled")) + { + time = 0; + } + + double init = 0; + if ( m_Timer.LastTick != DateTime.MinValue ) + init = time - ( DateTime.UtcNow - m_Timer.LastTick ).TotalSeconds; + time = init+time*m_Queue.Count; + if ( time < 0 ) + time = 0; + return String.Format( "{0:F1} seconds", time ); + } + else + { + return "0.0 seconds"; + } + } + } + + private class ProcTimer : Timer + { + private DateTime m_StartTime; + private DateTime m_LastTick; + + public DateTime LastTick { get { return m_LastTick; } } + + public ProcTimer() : base( TimeSpan.Zero, TimeSpan.Zero ) + { + } + + public void StartMe() + { + m_LastTick = DateTime.UtcNow; + m_StartTime = DateTime.UtcNow; + + OnTick(); + + Delay = Interval; + + Start(); + } + + protected override void OnTick() + { + ArrayList requeue = null; + + m_LastTick = DateTime.UtcNow; + + if ( m_Queue != null && m_Queue.Count > 0 ) + { + this.Interval = TimeSpan.FromMilliseconds(Config.GetBool("ObjectDelayEnabled") ? Config.GetInt("ObjectDelay") : 0); + + //this.Interval = TimeSpan.FromMilliseconds( Config.GetInt( "ObjectDelay" ) ); + + while ( m_Queue.Count > 0 ) + { + Serial s = (Serial)m_Queue.Peek(); + if ( s == Serial.Zero ) // dragdrop action + { + DragDropManager.ProcStatus status = DragDropManager.ProcessNext( m_Queue.Count - 1 ); + if ( status != DragDropManager.ProcStatus.KeepWaiting ) + { + m_Queue.Dequeue(); // if not waiting then dequeue it + + if ( status == DragDropManager.ProcStatus.ReQueue ) + m_Queue.Enqueue( s ); + } + + if ( status == DragDropManager.ProcStatus.KeepWaiting || status == DragDropManager.ProcStatus.Success ) + break; // don't process more if we're waiting or we just processed something + } + else + { + m_Queue.Dequeue(); + ClientCommunication.SendToServer( new DoubleClick( s ) ); + break; + } + } + + if ( requeue != null ) + { + for(int i=0;i s.ShowMe()); + + if (m_Parent != null) + m_Parent.Update(); + } + } + + public class DoubleClickTypeAction : MacroAction + { + private ushort m_Gfx; + public bool m_Item; + + public DoubleClickTypeAction(string[] args) + { + m_Gfx = Convert.ToUInt16(args[1]); + try + { + m_Item = Convert.ToBoolean(args[2]); + } + catch + { + } + } + + public DoubleClickTypeAction(ushort gfx, bool item) + { + m_Gfx = gfx; + m_Item = item; + } + + public override bool Perform() + { + Serial click = Serial.Zero; + + if (m_Item) + { + Item item = World.Player.Backpack != null ? World.Player.Backpack.FindItemByID(m_Gfx) : null; + ArrayList list = new ArrayList(); + if (item == null) + { + foreach (Item i in World.Items.Values) + { + if (i.ItemID == m_Gfx && i.RootContainer == null) + { + if (Config.GetBool("RangeCheckDoubleClick")) + { + if (Utility.InRange(World.Player.Position, i.Position, 2)) + { + list.Add(i); + } + } + else + { + list.Add(i); + } + } + + } + if (list.Count == 0) + { + foreach (Item i in World.Items.Values) + { + if (i.ItemID == m_Gfx && !i.IsInBank) + { + if (Config.GetBool("RangeCheckDoubleClick")) + { + if (Utility.InRange(World.Player.Position, i.Position, 2)) + { + list.Add(i); + } + } + else + { + list.Add(i); + } + } + } + } + + if (list.Count > 0) + click = ((Item)list[Utility.Random(list.Count)]).Serial; + } + else + { + click = item.Serial; + } + } + else + { + ArrayList list = new ArrayList(); + foreach (Mobile m in World.MobilesInRange()) + { + if (m.Body == m_Gfx) + { + if (Config.GetBool("RangeCheckDoubleClick")) + { + if (Utility.InRange(World.Player.Position, m.Position, 2)) + { + list.Add(m); + } + } + else + { + list.Add(m); + } + } + } + + if (list.Count > 0) + click = ((Mobile)list[Utility.Random(list.Count)]).Serial; + } + + if (click != Serial.Zero) + PlayerData.DoubleClick(click); + else + World.Player.SendMessage(MsgLevel.Force, LocString.NoItemOfType, m_Item ? ((ItemID)m_Gfx).ToString() : String.Format("(Character) 0x{0:X}", m_Gfx)); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Gfx, m_Item); + } + + public override string ToString() + { + return Language.Format(LocString.DClickA1, m_Item ? ((ItemID)m_Gfx).ToString() : String.Format("(Character) 0x{0:X}", m_Gfx)); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ReTarget, new MacroMenuCallback( ReTarget ) ) + }; + } + + return m_MenuItems; + } + + private void ReTarget(object[] args) + { + Targeting.OneTimeTarget(new Targeting.TargetResponseCallback(OnReTarget)); + World.Player.SendMessage(LocString.SelTargAct); + } + + private void OnReTarget(bool ground, Serial serial, Point3D pt, ushort gfx) + { + m_Gfx = gfx; + m_Item = serial.IsItem; + + Engine.MainWindow.SafeAction(s => s.ShowMe()); + if (m_Parent != null) + m_Parent.Update(); + } + } + + public class LiftAction : MacroWaitAction + { + private ushort m_Amount; + private Serial m_Serial; + private ushort m_Gfx; + + private static Item m_LastLift; + public static Item LastLift { get { return m_LastLift; } set { m_LastLift = value; } } + + public LiftAction(string[] args) + { + m_Serial = Serial.Parse(args[1]); + m_Amount = Convert.ToUInt16(args[2]); + m_Gfx = Convert.ToUInt16(args[3]); + } + + public LiftAction(Serial ser, ushort amount, ushort gfx) + { + m_Serial = ser; + m_Amount = amount; + m_Gfx = gfx; + } + + private int m_Id; + + public override bool Perform() + { + Item item = World.FindItem(m_Serial); + if (item != null) + { + //DragDropManager.Holding = item; + m_LastLift = item; + m_Id = DragDropManager.Drag(item, m_Amount <= item.Amount ? m_Amount : item.Amount); + } + else + { + World.Player.SendMessage(MsgLevel.Warning, LocString.MacroItemOutRange); + } + return false; + } + + public override bool PerformWait() + { + return DragDropManager.LastIDLifted < m_Id; + } + + public override string Serialize() + { + return DoSerialize(m_Serial.Value, m_Amount, m_Gfx); + } + + public override string ToString() + { + return Language.Format(LocString.LiftA10, m_Serial, m_Amount); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ConvLiftByType, new MacroMenuCallback( ConvertToByType ) ), + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( EditAmount ) ) + }; + } + + return m_MenuItems; + } + + private void EditAmount(object[] args) + { + if (InputBox.Show(Engine.MainWindow, Language.GetString(LocString.EnterAmount), Language.GetString(LocString.InputReq), m_Amount.ToString())) + { + m_Amount = (ushort)InputBox.GetInt(m_Amount); + + if (m_Parent != null) + m_Parent.Update(); + } + } + + private void ConvertToByType(object[] args) + { + if (m_Gfx != 0 && m_Parent != null) + m_Parent.Convert(this, new LiftTypeAction(m_Gfx, m_Amount)); + } + } + + public class LiftTypeAction : MacroWaitAction + { + private ushort m_Gfx; + private ushort m_Amount; + + public LiftTypeAction(string[] args) + { + m_Gfx = Convert.ToUInt16(args[1]); + m_Amount = Convert.ToUInt16(args[2]); + } + + public LiftTypeAction(ushort gfx, ushort amount) + { + m_Gfx = gfx; + m_Amount = amount; + } + + private int m_Id; + public override bool Perform() + { + Item item = World.Player.Backpack != null ? World.Player.Backpack.FindItemByID(m_Gfx) : null; + /*if ( item == null ) + { + ArrayList list = new ArrayList(); + + foreach ( Item i in World.Items.Values ) + { + if ( i.ItemID == m_Gfx && ( i.RootContainer == null || i.IsChildOf( World.Player.Quiver ) ) ) + list.Add( i ); + } + + if ( list.Count > 0 ) + item = (Item)list[ Utility.Random( list.Count ) ]; + }*/ + + if (item != null) + { + //DragDropManager.Holding = item; + ushort amount = m_Amount; + if (item.Amount < amount) + amount = item.Amount; + LiftAction.LastLift = item; + //ActionQueue.Enqueue( new LiftRequest( item, amount ) ); + m_Id = DragDropManager.Drag(item, amount); + } + else + { + World.Player.SendMessage(MsgLevel.Warning, LocString.NoItemOfType, (ItemID)m_Gfx); + //MacroManager.Stop(); + } + return false; + } + + public override bool PerformWait() + { + return DragDropManager.LastIDLifted < m_Id && !DragDropManager.Empty; + } + + public override string Serialize() + { + return DoSerialize(m_Gfx, m_Amount); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( EditAmount ) ) + }; + } + + return m_MenuItems; + } + + private void EditAmount(object[] args) + { + if (InputBox.Show(Engine.MainWindow, Language.GetString(LocString.EnterAmount), Language.GetString(LocString.InputReq), m_Amount.ToString())) + { + m_Amount = (ushort)InputBox.GetInt(m_Amount); + + if (m_Parent != null) + m_Parent.Update(); + } + } + + public override string ToString() + { + return Language.Format(LocString.LiftA10, m_Amount, (ItemID)m_Gfx); + } + } + + public class DropAction : MacroAction + { + private Serial m_To; + private Point3D m_At; + private Layer m_Layer; + + public DropAction(string[] args) + { + m_To = Serial.Parse(args[1]); + m_At = Point3D.Parse(args[2]); + try + { + m_Layer = (Layer)Byte.Parse(args[3]); + } + catch + { + m_Layer = Layer.Invalid; + } + } + + public DropAction(Serial to, Point3D at) : this(to, at, 0) + { + } + + public DropAction(Serial to, Point3D at, Layer layer) + { + m_To = to; + m_At = at; + m_Layer = layer; + } + + public override bool Perform() + { + if (DragDropManager.Holding != null) + { + if (m_Layer > Layer.Invalid && m_Layer <= Layer.LastUserValid) + { + Mobile m = World.FindMobile(m_To); + if (m != null) + DragDropManager.Drop(DragDropManager.Holding, m, m_Layer); + } + else + { + DragDropManager.Drop(DragDropManager.Holding, m_To, m_At); + } + } + else + { + World.Player.SendMessage(MsgLevel.Warning, LocString.MacroNoHold); + } + return true; + } + + public override string Serialize() + { + return DoSerialize(m_To, m_At, (byte)m_Layer); + } + + public override string ToString() + { + if (m_Layer != Layer.Invalid) + return Language.Format(LocString.EquipTo, m_To, m_Layer); + else + return Language.Format(LocString.DropA2, m_To.IsValid ? m_To.ToString() : "Ground", m_At); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_To.IsValid) + { + return null; // Dont allow conversion(s) + } + else + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ConvRelLoc, new MacroMenuCallback( ConvertToRelLoc ) ) + }; + } + + return m_MenuItems; + } + } + + private void ConvertToRelLoc(object[] args) + { + if (!m_To.IsValid && m_Parent != null) + m_Parent.Convert(this, new DropRelLocAction((sbyte)(m_At.X - World.Player.Position.X), (sbyte)(m_At.Y - World.Player.Position.Y), (sbyte)(m_At.Z - World.Player.Position.Z))); + } + } + + public class DropRelLocAction : MacroAction + { + private sbyte[] m_Loc; + + public DropRelLocAction(string[] args) + { + m_Loc = new sbyte[3] + { + Convert.ToSByte( args[1] ), + Convert.ToSByte( args[2] ), + Convert.ToSByte( args[3] ) + }; + } + + public DropRelLocAction(sbyte x, sbyte y, sbyte z) + { + m_Loc = new sbyte[3] { x, y, z }; + } + + public override bool Perform() + { + if (DragDropManager.Holding != null) + DragDropManager.Drop(DragDropManager.Holding, null, new Point3D((ushort)(World.Player.Position.X + m_Loc[0]), (ushort)(World.Player.Position.Y + m_Loc[1]), (short)(World.Player.Position.Z + m_Loc[2]))); + else + World.Player.SendMessage(LocString.MacroNoHold); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Loc[0], m_Loc[1], m_Loc[2]); + } + + public override string ToString() + { + return Language.Format(LocString.DropRelA3, m_Loc[0], m_Loc[1], m_Loc[2]); + } + } + + public class GumpResponseAction : MacroAction + { + private int m_ButtonID; + private int[] m_Switches; + private GumpTextEntry[] m_TextEntries; + + public GumpResponseAction(string[] args) + { + m_ButtonID = Convert.ToInt32(args[1]); + m_Switches = new int[Convert.ToInt32(args[2])]; + for (int i = 0; i < m_Switches.Length; i++) + m_Switches[i] = Convert.ToInt32(args[3 + i]); + m_TextEntries = new GumpTextEntry[Convert.ToInt32(args[3 + m_Switches.Length])]; + for (int i = 0; i < m_TextEntries.Length; i++) + { + string[] split = args[4 + m_Switches.Length + i].Split('&'); + m_TextEntries[i].EntryID = Convert.ToUInt16(split[0]); + m_TextEntries[i].Text = split[1]; + } + } + + public GumpResponseAction(int button, int[] switches, GumpTextEntry[] entries) + { + m_ButtonID = button; + m_Switches = switches; + m_TextEntries = entries; + } + + public override bool Perform() + { + ClientCommunication.SendToClient(new CloseGump(World.Player.CurrentGumpI)); + ClientCommunication.SendToServer(new GumpResponse(World.Player.CurrentGumpS, World.Player.CurrentGumpI, m_ButtonID, m_Switches, m_TextEntries)); + World.Player.HasGump = false; + return true; + } + + public override string Serialize() + { + ArrayList list = new ArrayList(3 + m_Switches.Length + m_TextEntries.Length); + list.Add(m_ButtonID); + list.Add(m_Switches.Length); + list.AddRange(m_Switches); + list.Add(m_TextEntries.Length); + for (int i = 0; i < m_TextEntries.Length; i++) + list.Add(String.Format("{0}&{1}", m_TextEntries[i].EntryID, m_TextEntries[i].Text)); + return DoSerialize((object[])list.ToArray(typeof(object))); + } + + public override string ToString() + { + if (m_ButtonID != 0) + return Language.Format(LocString.GumpRespB, m_ButtonID); + else + return Language.Format(LocString.CloseGump); + } + + private MenuItem[] m_MenuItems; + + public override MenuItem[] GetContextMenuItems() + { + if (this.m_MenuItems == null) + this.m_MenuItems = (MenuItem[])new MacroMenuItem[] + { + new MacroMenuItem(LocString.UseLastGumpResponse, new MacroMenuCallback(this.UseLastResponse), new object[0]), + new MacroMenuItem(LocString.Edit, new MacroMenuCallback(this.Edit), new object[0]) + }; + return this.m_MenuItems; + } + + private void Edit(object[] args) + { + if (InputBox.Show(Language.GetString(LocString.EnterNewText), "Input Box", this.m_ButtonID.ToString())) + this.m_ButtonID = InputBox.GetInt(); + + Parent?.Update(); + } + + private void UseLastResponse(object[] args) + { + m_ButtonID = World.Player.LastGumpResponseAction.m_ButtonID; + m_Switches = World.Player.LastGumpResponseAction.m_Switches; + m_TextEntries = World.Player.LastGumpResponseAction.m_TextEntries; + + World.Player.SendMessage(MsgLevel.Force, "Set GumpResponse to last response"); + + Parent?.Update(); + } + } + + public class MenuResponseAction : MacroAction + { + private ushort m_Index, m_ItemID, m_Hue; + + public MenuResponseAction(string[] args) + { + m_Index = Convert.ToUInt16(args[1]); + m_ItemID = Convert.ToUInt16(args[2]); + m_Hue = Convert.ToUInt16(args[3]); + } + + public MenuResponseAction(ushort idx, ushort iid, ushort hue) + { + m_Index = idx; + m_ItemID = iid; + m_Hue = hue; + } + + public override bool Perform() + { + ClientCommunication.SendToServer(new MenuResponse(World.Player.CurrentMenuS, World.Player.CurrentMenuI, m_Index, m_ItemID, m_Hue)); + World.Player.HasMenu = false; + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Index, m_ItemID, m_Hue); + } + + public override string ToString() + { + return Language.Format(LocString.MenuRespA1, m_Index); + } + } + + public class AbsoluteTargetAction : MacroAction + { + private TargetInfo m_Info; + + public AbsoluteTargetAction(string[] args) + { + m_Info = new TargetInfo(); + + m_Info.Type = Convert.ToByte(args[1]); + m_Info.Flags = Convert.ToByte(args[2]); + m_Info.Serial = Convert.ToUInt32(args[3]); + m_Info.X = Convert.ToUInt16(args[4]); + m_Info.Y = Convert.ToUInt16(args[5]); + m_Info.Z = Convert.ToInt16(args[6]); + m_Info.Gfx = Convert.ToUInt16(args[7]); + } + + public AbsoluteTargetAction(TargetInfo info) + { + m_Info = new TargetInfo(); + m_Info.Type = info.Type; + m_Info.Flags = info.Flags; + m_Info.Serial = info.Serial; + m_Info.X = info.X; + m_Info.Y = info.Y; + m_Info.Z = info.Z; + m_Info.Gfx = info.Gfx; + } + + public override bool Perform() + { + Targeting.Target(m_Info); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Info.Type, m_Info.Flags, m_Info.Serial.Value, m_Info.X, m_Info.Y, m_Info.Z, m_Info.Gfx); + } + + public override string ToString() + { + return Language.GetString(LocString.AbsTarg); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ReTarget, new MacroMenuCallback( ReTarget ) ), + new MacroMenuItem( LocString.ConvLT, new MacroMenuCallback( ConvertToLastTarget ) ), + new MacroMenuItem( LocString.ConvTargType, new MacroMenuCallback( ConvertToByType ) ), + new MacroMenuItem( LocString.ConvRelLoc, new MacroMenuCallback( ConvertToRelLoc ) ) + }; + } + + return m_MenuItems; + } + + private void ReTarget(object[] args) + { + Targeting.OneTimeTarget(!m_Info.Serial.IsValid, new Targeting.TargetResponseCallback(ReTargetResponse)); + World.Player.SendMessage(MsgLevel.Force, LocString.SelTargAct); + } + + private void ReTargetResponse(bool ground, Serial serial, Point3D pt, ushort gfx) + { + m_Info.Gfx = gfx; + m_Info.Serial = serial; + m_Info.Type = (byte)(ground ? 1 : 0); + m_Info.X = pt.X; + m_Info.Y = pt.Y; + m_Info.Z = pt.Z; + + Engine.MainWindow.SafeAction(s => s.ShowMe()); + if (m_Parent != null) + m_Parent.Update(); + } + + private void ConvertToLastTarget(object[] args) + { + if (m_Parent != null) + m_Parent.Convert(this, new LastTargetAction()); + } + + private void ConvertToByType(object[] args) + { + if (m_Parent != null) + m_Parent.Convert(this, new TargetTypeAction(m_Info.Serial.IsMobile, m_Info.Gfx)); + } + + private void ConvertToRelLoc(object[] args) + { + if (m_Parent != null) + m_Parent.Convert(this, new TargetRelLocAction((sbyte)(m_Info.X - World.Player.Position.X), (sbyte)(m_Info.Y - World.Player.Position.Y)));//, (sbyte)(m_Info.Z - World.Player.Position.Z) ) ); + } + } + + /// + /// Action to handle variable macros to alleviate the headache of having multiple macros for the same thing + /// + /// This Action does break the pattern that you see in every other action because the data that is stored for this + /// action exists not in the Macro file, but in a different file that has all the variables + /// + /// TODO: Re-eval this concept and instead store all data + /// + public class AbsoluteTargetVariableAction : MacroAction + { + private TargetInfo _target; + private readonly string _variableName; + + public AbsoluteTargetVariableAction(string[] args) + { + _variableName = args[1]; + } + + public override bool Perform() + { + _target = null; + + foreach (AbsoluteTargets.AbsoluteTarget at in AbsoluteTargets.AbsoluteTargetList) + { + if (at.TargetVariableName.Equals(_variableName)) + { + _target = at.TargetInfo; + break; + } + } + + if (_target != null) + { + Targeting.Target(_target); + return true; + } + else + { + return false; + } + + + } + + public override string Serialize() + { + return DoSerialize(_variableName); + } + + public override string ToString() + { + return $"{Language.GetString(LocString.AbsTarg)} (${_variableName})"; + } + + /*private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ReTarget, ReTarget ) + }; + } + + return m_MenuItems; + } + + private void ReTarget(object[] args) + { + Targeting.OneTimeTarget(!_target.Serial.IsValid, new Targeting.TargetResponseCallback(ReTargetResponse)); + World.Player.SendMessage(MsgLevel.Force, LocString.SelTargAct); + } + + private void ReTargetResponse(bool ground, Serial serial, Point3D pt, ushort gfx) + { + _target.Gfx = gfx; + _target.Serial = serial; + _target.Type = (byte)(ground ? 1 : 0); + _target.X = pt.X; + _target.Y = pt.Y; + _target.Z = pt.Z; + + Engine.MainWindow.SafeAction(s => s.ShowMe()); + + m_Parent?.Update(); + }*/ + } + + public class TargetTypeAction : MacroAction + { + private bool m_Mobile; + private ushort m_Gfx; + private object _previousObject; + + public TargetTypeAction(string[] args) + { + m_Mobile = Convert.ToBoolean(args[1]); + m_Gfx = Convert.ToUInt16(args[2]); + } + + public TargetTypeAction(bool mobile, ushort gfx) + { + m_Mobile = mobile; + m_Gfx = gfx; + } + + public override bool Perform() + { + ArrayList list = new ArrayList(); + if (m_Mobile) + { + foreach (Mobile find in World.MobilesInRange()) + { + if (find.Body == m_Gfx) + { + if (Config.GetBool("RangeCheckTargetByType")) + { + if (Utility.InRange(World.Player.Position, find.Position, 2)) + { + list.Add(find); + } + } + else + { + list.Add(find); + } + } + } + } + else + { + foreach (Item i in World.Items.Values) + { + if (i.ItemID == m_Gfx && !i.IsInBank) + { + if (Config.GetBool("RangeCheckTargetByType")) + { + if (Utility.InRange(World.Player.Position, i.Position, 2)) + { + list.Add(i); + } + } + else + { + list.Add(i); + } + } + + } + } + + if (list.Count > 0) + { + if (Config.GetBool("DiffTargetByType") && list.Count > 1) + { + object currentObject = list[Utility.Random(list.Count)]; + + while (_previousObject != null && _previousObject == currentObject) + { + currentObject = list[Utility.Random(list.Count)]; + } + + Targeting.Target(currentObject); + + _previousObject = currentObject; + } + else + { + Targeting.Target(list[Utility.Random(list.Count)]); + } + + } + else + { + World.Player.SendMessage(MsgLevel.Warning, LocString.NoItemOfType, + m_Mobile ? String.Format("Character [{0}]", m_Gfx) : ((ItemID)m_Gfx).ToString()); + } + + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Mobile, m_Gfx); + } + + public override string ToString() + { + if (m_Mobile) + return Language.Format(LocString.TargByType, m_Gfx); + else + return Language.Format(LocString.TargByType, (ItemID)m_Gfx); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ReTarget, new MacroMenuCallback( ReTarget ) ), + new MacroMenuItem( LocString.ConvLT, new MacroMenuCallback( ConvertToLastTarget ) ) + }; + } + + return m_MenuItems; + } + + private void ReTarget(object[] args) + { + Targeting.OneTimeTarget(false, new Targeting.TargetResponseCallback(ReTargetResponse)); + World.Player.SendMessage(MsgLevel.Force, LocString.SelTargAct); + } + + private void ReTargetResponse(bool ground, Serial serial, Point3D pt, ushort gfx) + { + if (!ground && serial.IsValid) + { + m_Mobile = serial.IsMobile; + m_Gfx = gfx; + } + Engine.MainWindow.SafeAction(s => s.ShowMe()); + if (m_Parent != null) + m_Parent.Update(); + } + + private void ConvertToLastTarget(object[] args) + { + if (m_Parent != null) + m_Parent.Convert(this, new LastTargetAction()); + } + } + + public class TargetRelLocAction : MacroAction + { + private sbyte m_X, m_Y; + + public TargetRelLocAction(string[] args) + { + m_X = Convert.ToSByte(args[1]); + m_Y = Convert.ToSByte(args[2]); + } + + public TargetRelLocAction(sbyte x, sbyte y) + { + m_X = x; + m_Y = y; + } + + public override bool Perform() + { + ushort x = (ushort)(World.Player.Position.X + m_X); + ushort y = (ushort)(World.Player.Position.Y + m_Y); + short z = (short)World.Player.Position.Z; + try + { + Ultima.HuedTile tile = Map.GetTileNear(World.Player.Map, x, y, z); + Targeting.Target(new Point3D(x, y, tile.Z), (ushort)tile.ID); + } + catch (Exception e) + { + World.Player.SendMessage(MsgLevel.Debug, "Error Executing TargetRelLoc: {0}", e.Message); + } + return true; + } + + public override string Serialize() + { + return DoSerialize(m_X, m_Y); + } + + public override string ToString() + { + return Language.Format(LocString.TargRelLocA3, m_X, m_Y, 0); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.ReTarget, new MacroMenuCallback( ReTarget ) ) + }; + } + + return m_MenuItems; + } + + private void ReTarget(object[] args) + { + Engine.MainWindow.SafeAction(s => s.ShowMe()); + + Targeting.OneTimeTarget(true, new Targeting.TargetResponseCallback(ReTargetResponse)); + World.Player.SendMessage(LocString.SelTargAct); + } + + private void ReTargetResponse(bool ground, Serial serial, Point3D pt, ushort gfx) + { + m_X = (sbyte)(pt.X - World.Player.Position.X); + m_Y = (sbyte)(pt.Y - World.Player.Position.Y); + // m_Z = (sbyte)(pt.Z - World.Player.Position.Z); + if (m_Parent != null) + m_Parent.Update(); + } + } + + public class LastTargetAction : MacroAction + { + public LastTargetAction() + { + } + + public override bool Perform() + { + if (!Targeting.DoLastTarget())//Targeting.LastTarget( true ); + Targeting.ResendTarget(); + return true; + } + + public override string ToString() + { + return String.Format("Exec: {0}", Language.GetString(LocString.LastTarget)); + } + } + + public class SetLastTargetAction : MacroWaitAction + { + public SetLastTargetAction() + { + } + + public override bool Perform() + { + Targeting.TargetSetLastTarget(); + return !PerformWait(); + } + + public override bool PerformWait() + { + return !Targeting.LTWasSet; + } + + public override string ToString() + { + return Language.GetString(LocString.SetLT); + } + } + + public class SpeechAction : MacroAction + { + private MessageType m_Type; + private ushort m_Font; + private ushort m_Hue; + private string m_Lang; + private ArrayList m_Keywords; + private string m_Speech; + + public SpeechAction(string[] args) + { + m_Type = ((MessageType)Convert.ToInt32(args[1])) & ~MessageType.Encoded; + m_Hue = Convert.ToUInt16(args[2]); + m_Font = Convert.ToUInt16(args[3]); + m_Lang = args[4]; + + int count = Convert.ToInt32(args[5]); + if (count > 0) + { + m_Keywords = new ArrayList(count); + m_Keywords.Add(Convert.ToUInt16(args[6])); + + for (int i = 1; i < count; i++) + m_Keywords.Add(Convert.ToByte(args[6 + i])); + } + + m_Speech = args[6 + count]; + } + + public SpeechAction(MessageType type, ushort hue, ushort font, string lang, ArrayList kw, string speech) + { + m_Type = type; + m_Hue = hue; + m_Font = font; + m_Lang = lang; + m_Keywords = kw; + m_Speech = speech; + } + + public override bool Perform() + { + if (m_Speech.Length > 1 && m_Speech[0] == '-') + { + string text = m_Speech.Substring(1); + string[] split = text.Split(' ', '\t'); + CommandCallback call = (CommandCallback)Command.List[split[0]]; + if (call == null && text[0] == '-') + { + call = (CommandCallback)Command.List["-"]; + if (call != null && split.Length > 1 && split[1] != null && split[1].Length > 1) + split[1] = split[1].Substring(1); + } + + if (call != null) + { + ArrayList list = new ArrayList(); + for (int i = 1; i < split.Length; i++) + { + if (split[i] != null && split[i].Length > 0) + list.Add(split[i]); + } + call((string[])list.ToArray(typeof(string))); + return true; + } + } + + int hue = m_Hue; + + if (m_Type != MessageType.Emote) + { + if (World.Player.SpeechHue == 0) + World.Player.SpeechHue = m_Hue; + hue = World.Player.SpeechHue; + } + + ClientCommunication.SendToServer(new ClientUniMessage(m_Type, hue, m_Font, m_Lang, m_Keywords, m_Speech)); + return true; + } + + public override string Serialize() + { + ArrayList list = new ArrayList(6); + list.Add((int)m_Type); + list.Add(m_Hue); + list.Add(m_Font); + list.Add(m_Lang); + if (m_Keywords != null && m_Keywords.Count > 1) + { + list.Add((int)m_Keywords.Count); + for (int i = 0; i < m_Keywords.Count; i++) + list.Add(m_Keywords[i]); + } + else + { + list.Add("0"); + } + list.Add(m_Speech); + + return DoSerialize((object[])list.ToArray(typeof(object))); + } + + public override string ToString() + { + //return Language.Format( LocString.SayQA1, m_Speech ); + StringBuilder sb = new StringBuilder(); + switch (m_Type) + { + case MessageType.Emote: + sb.Append("Emote: "); + break; + case MessageType.Whisper: + sb.Append("Whisper: "); + break; + case MessageType.Yell: + sb.Append("Yell: "); + break; + case MessageType.Regular: + default: + sb.Append("Say: "); + break; + } + sb.Append(m_Speech); + return sb.ToString(); + } + + private MenuItem[] m_MenuItems; + + public override MenuItem[] GetContextMenuItems() + { + if (this.m_MenuItems == null) + this.m_MenuItems = (MenuItem[])new MacroMenuItem[1] + { + new MacroMenuItem(LocString.Edit, new MacroMenuCallback(this.Edit), new object[0]) + }; + return this.m_MenuItems; + } + + private void Edit(object[] args) + { + if (InputBox.Show(Language.GetString(LocString.EnterNewText), "Input Box", this.m_Speech)) + this.m_Speech = InputBox.GetString(); + if (this.Parent == null) + return; + this.Parent.Update(); + } + } + + public class UseSkillAction : MacroAction + { + private int m_Skill; + + public UseSkillAction(string[] args) + { + m_Skill = Convert.ToInt32(args[1]); + } + + public UseSkillAction(int sk) + { + m_Skill = sk; + } + + public override bool Perform() + { + ClientCommunication.SendToServer(new UseSkill(m_Skill)); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Skill); + } + + public override string ToString() + { + return Language.Format(LocString.UseSkillA1, Language.Skill2Str(m_Skill)); + } + } + + public class ExtCastSpellAction : MacroAction + { + private Spell m_Spell; + private Serial m_Book; + + public ExtCastSpellAction(string[] args) + { + m_Spell = Spell.Get(Convert.ToInt32(args[1])); + m_Book = Serial.Parse(args[2]); + } + + public ExtCastSpellAction(int s, Serial book) + { + m_Spell = Spell.Get(s); + m_Book = book; + } + + public ExtCastSpellAction(Spell s, Serial book) + { + m_Spell = s; + m_Book = book; + } + + public override bool Perform() + { + m_Spell.OnCast(new ExtCastSpell(m_Book, (ushort)m_Spell.GetID())); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Spell.GetID(), m_Book.Value); + } + + public override string ToString() + { + return Language.Format(LocString.CastSpellA1, m_Spell); + } + } + + public class BookCastSpellAction : MacroAction + { + private Spell m_Spell; + private Serial m_Book; + + public BookCastSpellAction(string[] args) + { + m_Spell = Spell.Get(Convert.ToInt32(args[1])); + m_Book = Serial.Parse(args[2]); + } + + public BookCastSpellAction(int s, Serial book) + { + m_Spell = Spell.Get(s); + m_Book = book; + } + + public BookCastSpellAction(Spell s, Serial book) + { + m_Spell = s; + m_Book = book; + } + + public override bool Perform() + { + m_Spell.OnCast(new CastSpellFromBook(m_Book, (ushort)m_Spell.GetID())); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Spell.GetID(), m_Book.Value); + } + + public override string ToString() + { + return Language.Format(LocString.CastSpellA1, m_Spell); + } + } + + public class MacroCastSpellAction : MacroAction + { + private Spell m_Spell; + + public MacroCastSpellAction(string[] args) + { + m_Spell = Spell.Get(Convert.ToInt32(args[1])); + } + + public MacroCastSpellAction(int s) + { + m_Spell = Spell.Get(s); + } + + public MacroCastSpellAction(Spell s) + { + m_Spell = s; + } + + public override bool Perform() + { + m_Spell.OnCast(new CastSpellFromMacro((ushort)m_Spell.GetID())); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Spell.GetID()); + } + + public override string ToString() + { + return Language.Format(LocString.CastSpellA1, m_Spell); + } + } + + public class SetAbilityAction : MacroAction + { + private AOSAbility m_Ability; + + public SetAbilityAction(string[] args) + { + m_Ability = (AOSAbility)Convert.ToInt32(args[1]); + } + + public SetAbilityAction(AOSAbility a) + { + m_Ability = a; + } + + public override bool Perform() + { + ClientCommunication.SendToServer(new UseAbility(m_Ability)); + return true; + } + + public override string Serialize() + { + return DoSerialize((int)m_Ability); + } + + public override string ToString() + { + return Language.Format(LocString.SetAbilityA1, m_Ability); + } + } + + public class DressAction : MacroWaitAction + { + private string m_Name; + + public DressAction(string[] args) + { + m_Name = args[1]; + } + + public DressAction(string name) + { + m_Name = name; + } + + public override bool Perform() + { + DressList list = DressList.Find(m_Name); + if (list != null) + { + list.Dress(); + return false; + } + else + { + return true; + } + } + + public override bool PerformWait() + { + return !ActionQueue.Empty; + } + + public override string Serialize() + { + return DoSerialize(m_Name); + } + + public override string ToString() + { + return Language.Format(LocString.DressA1, m_Name); + } + } + + public class UnDressAction : MacroWaitAction + { + private string m_Name; + private byte m_Layer; + + public UnDressAction(string[] args) + { + try + { + m_Layer = Convert.ToByte(args[2]); + } + catch + { + m_Layer = 255; + } + + if (m_Layer == 255) + m_Name = args[1]; + else + m_Name = ""; + } + + public UnDressAction(string name) + { + m_Name = name; + m_Layer = 255; + } + + public UnDressAction(byte layer) + { + m_Layer = layer; + m_Name = ""; + } + + public override bool Perform() + { + if (m_Layer == 255) + { + DressList list = DressList.Find(m_Name); + if (list != null) + { + list.Undress(); + return false; + } + else + { + return true; + } + } + else if (m_Layer == 0) + { + HotKeys.UndressHotKeys.OnUndressAll(); + return false; + } + else + { + return !HotKeys.UndressHotKeys.Unequip((Layer)m_Layer); + } + } + + public override bool PerformWait() + { + return !ActionQueue.Empty; + } + + public override string Serialize() + { + return DoSerialize(m_Name, m_Layer); + } + + public override string ToString() + { + if (m_Layer == 255) + return Language.Format(LocString.UndressA1, m_Name); + else if (m_Layer == 0) + return Language.GetString(LocString.UndressAll); + else + return Language.Format(LocString.UndressLayerA1, (Layer)m_Layer); + } + } + + public class WalkAction : MacroWaitAction + { + private Direction m_Dir; + private static DateTime m_LastWalk = DateTime.MinValue; + + private enum KeyboardDir { + North = 0x21, //page up + Right = 0x27, // right + East = 0x22, // page down + Down = 0x28, // down + South = 0x23, // end + Left = 0x25, // left + West = 0x24, // home + Up = 0x26, // up + } + + private static uint WM_KEYDOWN = 0x100, WM_KEYUP = 0x101; + + [DllImport("user32.dll")] + private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + + public static DateTime LastWalkTime { get { return m_LastWalk; } set { m_LastWalk = value; } } + + public WalkAction(string[] args) + { + m_Dir = (Direction)(Convert.ToByte(args[1])) & Direction.Mask; + } + + public WalkAction(Direction dir) + { + m_Dir = dir & Direction.Mask; + } + + //private static int m_LastSeq = -1; + public override bool Perform() + { + return !PerformWait(); + } + + //public static bool IsMacroWalk(byte seq) + //{ + // return m_LastSeq != -1 && m_LastSeq == (int)seq && World.Player.HasWalkEntry((byte)m_LastSeq); + //} + + public override bool PerformWait() + { + if (m_LastWalk + TimeSpan.FromSeconds(0.4) >= DateTime.UtcNow) + { + return true; + } + else + { + m_LastSeq = World.Player.WalkSequence; + m_LastWalk = DateTime.UtcNow; + + //ClientCommunication.SendToClient(new ForceWalk(m_Dir)); + ClientCommunication.SendToServer(new WalkRequest(m_Dir, World.Player.WalkSequence++)); + + + + return false; + } + } + + public override string Serialize() + { + return DoSerialize((byte)m_Dir); + } + + public override string ToString() + { + return Language.Format(LocString.WalkA1, m_Dir != Direction.Mask ? m_Dir.ToString() : "Up"); + } + } + + public class WaitForMenuAction : MacroWaitAction + { + private uint m_MenuID; + + public WaitForMenuAction(uint gid) + { + m_MenuID = gid; + } + + public WaitForMenuAction(string[] args) + { + if (args.Length > 1) + m_MenuID = Convert.ToUInt32(args[1]); + + try + { + m_Timeout = TimeSpan.FromSeconds(Convert.ToDouble(args[2])); + } + catch + { + } + } + + public override bool Perform() + { + return !PerformWait(); + } + + public override bool PerformWait() + { + return !(World.Player.HasMenu && (World.Player.CurrentGumpI == m_MenuID || m_MenuID == 0)); + } + + public override string ToString() + { + if (m_MenuID == 0) + return Language.GetString(LocString.WaitAnyMenu); + else + return Language.Format(LocString.WaitMenuA1, m_MenuID); + } + + public override string Serialize() + { + return DoSerialize(m_MenuID, m_Timeout.TotalSeconds); + } + + public override bool CheckMatch(MacroAction a) + { + if (a is WaitForMenuAction) + { + if (m_MenuID == 0 || ((WaitForMenuAction)a).m_MenuID == m_MenuID) + return true; + } + + return false; + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ), + this.EditTimeoutMenuItem + }; + } + + return m_MenuItems; + } + + private void Edit(object[] args) + { + new MacroInsertWait(this).ShowDialog(Engine.MainWindow); + } + } + + public class WaitForGumpAction : MacroWaitAction + { + private uint m_GumpID; + private bool m_Strict; + + public WaitForGumpAction() + { + m_GumpID = 0; + m_Strict = false; + } + + public WaitForGumpAction(uint gid) + { + m_GumpID = gid; + m_Strict = false; + } + + public WaitForGumpAction(string[] args) + { + m_GumpID = Convert.ToUInt32(args[1]); + try + { + m_Strict = Convert.ToBoolean(args[2]); + } + catch + { + m_Strict = false; + } + + try + { + m_Timeout = TimeSpan.FromSeconds(Convert.ToDouble(args[3])); + } + catch + { + } + } + + public override bool Perform() + { + return !PerformWait(); + } + + public override bool PerformWait() + { + return !(World.Player.HasGump && (World.Player.CurrentGumpI == m_GumpID || !m_Strict || m_GumpID == 0)); + + //if (!World.Player.HasGump) // Does the player even have a gump? + // return true; + + //if ((int)World.Player.CurrentGumpI != (int)m_GumpID && m_Strict) + // return m_GumpID > 0; + + //return false; + } + + public override string ToString() + { + if (m_GumpID == 0 || !m_Strict) + return Language.GetString(LocString.WaitAnyGump); + else + return Language.Format(LocString.WaitGumpA1, m_GumpID); + } + + public override string Serialize() + { + return DoSerialize(m_GumpID, m_Strict, m_Timeout.TotalSeconds); + } + + public override bool CheckMatch(MacroAction a) + { + if (a is WaitForGumpAction) + { + if (m_GumpID == 0 || ((WaitForGumpAction)a).m_GumpID == m_GumpID) + return true; + } + + return false; + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ), + new MacroMenuItem( LocString.Null, new MacroMenuCallback( ToggleStrict ) ), + this.EditTimeoutMenuItem + }; + } + + if (!m_Strict) + m_MenuItems[1].Text = String.Format("Change to \"{0}\"", Language.Format(LocString.WaitGumpA1, m_GumpID)); + else + m_MenuItems[1].Text = String.Format("Change to \"{0}\"", Language.GetString(LocString.WaitAnyGump)); + m_MenuItems[1].Enabled = m_GumpID != 0 || m_Strict; + + return m_MenuItems; + } + + private void Edit(object[] args) + { + new MacroInsertWait(this).ShowDialog(Engine.MainWindow); + } + + private void ToggleStrict(object[] args) + { + m_Strict = !m_Strict; + if (m_Parent != null) + m_Parent.Update(); + } + } + + public class WaitForTargetAction : MacroWaitAction + { + public WaitForTargetAction() + { + m_Timeout = TimeSpan.FromSeconds(30.0); + } + + public WaitForTargetAction(string[] args) + { + try + { + m_Timeout = TimeSpan.FromSeconds(Convert.ToDouble(args[1])); + } + catch + { + m_Timeout = TimeSpan.FromSeconds(30.0); + } + } + + public override bool Perform() + { + return !PerformWait(); + } + + public override bool PerformWait() + { + return !Targeting.HasTarget; + } + + public override string ToString() + { + return Language.GetString(LocString.WaitTarg); + } + + public override string Serialize() + { + return DoSerialize(m_Timeout.TotalSeconds); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ), + this.EditTimeoutMenuItem + }; + } + + return m_MenuItems; + } + + public override bool CheckMatch(MacroAction a) + { + return (a is WaitForTargetAction); + } + + private void Edit(object[] args) + { + new MacroInsertWait(this).ShowDialog(Engine.MainWindow); + } + } + + public class PauseAction : MacroWaitAction + { + public PauseAction(string[] args) + { + m_Timeout = TimeSpan.Parse(args[1]); + } + + public PauseAction(int ms) + { + m_Timeout = TimeSpan.FromMilliseconds(ms); + } + + public PauseAction(TimeSpan time) + { + m_Timeout = time; + } + + public override string Serialize() + { + return DoSerialize(m_Timeout); + } + + public override bool Perform() + { + this.StartTime = DateTime.UtcNow; + return !PerformWait(); + } + + public override bool PerformWait() + { + return (StartTime + m_Timeout >= DateTime.UtcNow); + } + + public override string ToString() + { + return Language.Format(LocString.PauseA1, m_Timeout.TotalSeconds); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ) + }; + } + + return m_MenuItems; + } + + private void Edit(object[] args) + { + new MacroInsertWait(this).ShowDialog(Engine.MainWindow); + } + } + + public class WaitForStatAction : MacroWaitAction + { + private byte m_Direction; + private int m_Value; + private IfAction.IfVarType m_Stat; + + public byte Op { get { return m_Direction; } } + public int Amount { get { return m_Value; } } + public IfAction.IfVarType Stat { get { return m_Stat; } } + + public WaitForStatAction(string[] args) + { + m_Stat = (IfAction.IfVarType)Convert.ToInt32(args[1]); + m_Direction = Convert.ToByte(args[2]); + m_Value = Convert.ToInt32(args[3]); + + try + { + m_Timeout = TimeSpan.FromSeconds(Convert.ToDouble(args[4])); + } + catch + { + m_Timeout = TimeSpan.FromMinutes(60.0); + } + } + + public WaitForStatAction(IfAction.IfVarType stat, byte dir, int val) + { + m_Stat = stat; + m_Direction = dir; + m_Value = val; + + m_Timeout = TimeSpan.FromMinutes(60.0); + } + + public override string Serialize() + { + return DoSerialize((int)m_Stat, m_Direction, m_Value, m_Timeout.TotalSeconds); + } + + public override bool Perform() + { + return !PerformWait(); + } + + public override bool PerformWait() + { + if (m_Direction > 0) + { + // wait for m_Stat >= m_Value + switch (m_Stat) + { + case IfAction.IfVarType.Hits: + return World.Player.Hits < m_Value; + case IfAction.IfVarType.Mana: + return World.Player.Mana < m_Value; + case IfAction.IfVarType.Stamina: + return World.Player.Stam < m_Value; + } + } + else + { + // wait for m_Stat <= m_Value + switch (m_Stat) + { + case IfAction.IfVarType.Hits: + return World.Player.Hits > m_Value; + case IfAction.IfVarType.Mana: + return World.Player.Mana > m_Value; + case IfAction.IfVarType.Stamina: + return World.Player.Stam > m_Value; + } + } + + return false; + } + + public override string ToString() + { + return Language.Format(LocString.WaitA3, m_Stat, m_Direction > 0 ? ">=" : "<=", m_Value); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ), + this.EditTimeoutMenuItem + }; + } + + return m_MenuItems; + } + + private void Edit(object[] args) + { + new MacroInsertWait(this).ShowDialog(Engine.MainWindow); + } + } + + public class IfAction : MacroAction + { + public enum IfVarType : int + { + Hits = 0, + Mana, + Stamina, + Poisoned, + SysMessage, + Weight, + Mounted, + RHandEmpty, + LHandEmpty, + + BeginCountersMarker, + + Counter = 50 + } + + private sbyte m_Direction; + private object m_Value; + private IfVarType m_Var; + private string m_Counter; + private Assistant.Counter m_CountObj; + + public sbyte Op { get { return m_Direction; } } + public object Value { get { return m_Value; } } + public IfVarType Variable { get { return m_Var; } } + public string Counter { get { return m_Counter; } } + + public IfAction(string[] args) + { + m_Var = (IfVarType)Convert.ToInt32(args[1]); + try + { + m_Direction = Convert.ToSByte(args[2]); + if (m_Direction > 1) + m_Direction = 0; + } + catch + { + m_Direction = -1; + } + + if (m_Var != IfVarType.SysMessage) + m_Value = Convert.ToInt32(args[3]); + else + m_Value = args[3].ToLower(); + + if (m_Var == IfVarType.Counter) + m_Counter = args[4]; + } + + public IfAction(IfVarType var, sbyte dir, int val) + { + m_Var = var; + m_Direction = dir; + m_Value = val; + } + + public IfAction(IfVarType var, sbyte dir, int val, string counter) + { + m_Var = var; + m_Direction = dir; + m_Value = val; + m_Counter = counter; + } + + public IfAction(IfVarType var, string text) + { + m_Var = var; + m_Value = text.ToLower(); + } + + public override string Serialize() + { + if (m_Var == IfVarType.Counter && m_Counter != null) + return DoSerialize((int)m_Var, m_Direction, m_Value, m_Counter); + else + return DoSerialize((int)m_Var, m_Direction, m_Value); + } + + public override bool Perform() + { + return true; + } + + public bool Evaluate() + { + switch (m_Var) + { + case IfVarType.Hits: + case IfVarType.Mana: + case IfVarType.Stamina: + case IfVarType.Weight: + { + int val = (int)m_Value; + if (m_Direction > 0) + { + // if stat >= m_Value + switch (m_Var) + { + case IfVarType.Hits: + return World.Player.Hits >= val; + case IfVarType.Mana: + return World.Player.Mana >= val; + case IfVarType.Stamina: + return World.Player.Stam >= val; + case IfVarType.Weight: + return World.Player.Weight >= val; + } + } + else + { + // if stat <= m_Value + switch (m_Var) + { + case IfVarType.Hits: + return World.Player.Hits <= val; + case IfVarType.Mana: + return World.Player.Mana <= val; + case IfVarType.Stamina: + return World.Player.Stam <= val; + case IfVarType.Weight: + return World.Player.Weight <= val; + } + } + + return false; + } + + case IfVarType.Poisoned: + { + if (Windows.AllowBit(FeatureBit.BlockHealPoisoned)) + return World.Player.Poisoned; + else + return false; + } + + case IfVarType.SysMessage: + { + string text = (string)m_Value; + for (int i = PacketHandlers.SysMessages.Count - 1; i >= 0; i--) + { + string sys = PacketHandlers.SysMessages[i]; + if (sys.IndexOf(text, StringComparison.OrdinalIgnoreCase) != -1) + { + PacketHandlers.SysMessages.RemoveRange(0, i + 1); + return true; + } + } + + return false; + } + + case IfVarType.Mounted: + { + return World.Player.GetItemOnLayer(Layer.Mount) != null; + } + + case IfVarType.RHandEmpty: + { + return World.Player.GetItemOnLayer(Layer.RightHand) == null; + } + + case IfVarType.LHandEmpty: + { + return World.Player.GetItemOnLayer(Layer.LeftHand) == null; + } + + case IfVarType.Counter: + { + if (m_CountObj == null) + { + foreach (Assistant.Counter c in Assistant.Counter.List) + { + if (c.Name == m_Counter) + { + m_CountObj = c; + break; + } + } + } + + if (m_CountObj == null || !m_CountObj.Enabled) + return false; + + if (m_Direction > 0) + return m_CountObj.Amount >= (int)m_Value; + else + return m_CountObj.Amount <= (int)m_Value; + } + + default: + return false; + } + } + + public override string ToString() + { + switch (m_Var) + { + case IfVarType.Hits: + case IfVarType.Mana: + case IfVarType.Stamina: + case IfVarType.Weight: + return String.Format("If ( {0} {1} {2} )", m_Var, m_Direction > 0 ? ">=" : "<=", m_Value); + case IfVarType.Poisoned: + return "If ( Poisoned )"; + case IfVarType.SysMessage: + { + string str = (string)m_Value; + if (str.Length > 10) + str = str.Substring(0, 7) + "..."; + return String.Format("If ( SysMessage \"{0}\" )", str); + } + case IfVarType.Mounted: + return "If ( Mounted )"; + case IfVarType.RHandEmpty: + return "If ( R-Hand Empty )"; + case IfVarType.LHandEmpty: + return "If ( L-Hand Empty )"; + case IfVarType.Counter: + return String.Format("If ( \"{0} count\" {1} {2} )", m_Counter, m_Direction > 0 ? ">=" : "<=", m_Value); + default: + return "If ( ??? )"; + } + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ) + }; + } + + return m_MenuItems; + } + + private void Edit(object[] args) + { + new MacroInsertIf(this).ShowDialog(Engine.MainWindow); + } + } + + public class ElseAction : MacroAction + { + public ElseAction() + { + } + + public override bool Perform() + { + return true; + } + + public override string ToString() + { + return "Else"; + } + } + + public class EndIfAction : MacroAction + { + public EndIfAction() + { + } + + public override bool Perform() + { + return true; + } + + public override string ToString() + { + return "End If"; + } + } + + public class HotKeyAction : MacroAction + { + private KeyData m_Key; + + public HotKeyAction(KeyData hk) + { + m_Key = hk; + } + + public HotKeyAction(string[] args) + { + try + { + int loc = Convert.ToInt32(args[1]); + if (loc != 0) + m_Key = HotKey.Get(loc); + } + catch + { + } + + if (m_Key == null) + m_Key = HotKey.Get(args[2]); + + if (m_Key == null) + throw new Exception("HotKey not found."); + } + + public override bool Perform() + { + if (Windows.AllowBit(FeatureBit.LoopingMacros) || m_Key.DispName.IndexOf(Language.GetString(LocString.PlayA1).Replace(@"{0}", "")) == -1) + m_Key.Callback(); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Key.LocName, m_Key.StrName == null ? "" : m_Key.StrName); + } + + public override string ToString() + { + return String.Format("Exec: {0}", m_Key.DispName); + } + } + + public class ForAction : MacroAction + { + private int m_Max, m_Count; + + public int Count { get { return m_Count; } set { m_Count = value; } } + public int Max { get { return m_Max; } } + + public ForAction(string[] args) + { + m_Max = Convert.ToInt32(args[1]); + } + + public ForAction(int max) + { + m_Max = max; + } + + public override string Serialize() + { + return DoSerialize(m_Max); + } + + public override bool Perform() + { + return true; + } + + public override string ToString() + { + return String.Format("For ( 1 to {0} )", m_Max); + } + + private MenuItem[] m_MenuItems; + public override MenuItem[] GetContextMenuItems() + { + if (m_MenuItems == null) + { + m_MenuItems = new MacroMenuItem[] + { + new MacroMenuItem( LocString.Edit, new MacroMenuCallback( Edit ) ) + }; + } + + return m_MenuItems; + } + + private void Edit(object[] args) + { + if (InputBox.Show(Language.GetString(LocString.NumIter), "Input Box", m_Max.ToString())) + m_Max = InputBox.GetInt(m_Max); + if (Parent != null) + Parent.Update(); + } + } + + public class EndForAction : MacroAction + { + public EndForAction() + { + } + + public override bool Perform() + { + return true; + } + + public override string ToString() + { + return "End For"; + } + } + + public class ContextMenuAction : MacroAction + { + private ushort m_CtxName; + private ushort m_Idx; + private Serial m_Entity; + + public ContextMenuAction(UOEntity ent, ushort idx, ushort ctxName) + { + m_Entity = ent != null ? ent.Serial : Serial.MinusOne; + + if (World.Player != null && World.Player.Serial == m_Entity) + m_Entity = Serial.Zero; + + m_Idx = idx; + m_CtxName = ctxName; + } + + public ContextMenuAction(string[] args) + { + m_Entity = Serial.Parse(args[1]); + m_Idx = Convert.ToUInt16(args[2]); + try + { + m_CtxName = Convert.ToUInt16(args[3]); + } + catch + { + } + } + + public override bool Perform() + { + Serial s = m_Entity; + + if (s == Serial.Zero && World.Player != null) + s = World.Player.Serial; + + ClientCommunication.SendToServer(new ContextMenuRequest(s)); + ClientCommunication.SendToServer(new ContextMenuResponse(s, m_Idx)); + return true; + } + + public override string Serialize() + { + return DoSerialize(m_Entity, m_Idx, m_CtxName); + } + + public override string ToString() + { + string ent; + + if (m_Entity == Serial.Zero) + ent = "(self)"; + else + ent = m_Entity.ToString(); + return String.Format("ContextMenu: {1} ({0})", ent, m_Idx); + } + } +} + diff --git a/Core/Actions.resx b/Core/Actions.resx new file mode 100644 index 0000000..dd0ea4d --- /dev/null +++ b/Core/Actions.resx @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.0.0.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/Core/BuffsDebuffs.cs b/Core/BuffsDebuffs.cs new file mode 100644 index 0000000..1c099e4 --- /dev/null +++ b/Core/BuffsDebuffs.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Assistant.Core +{ + public enum BuffIcon : ushort + { + DismountPrevention = 0x3E9, + NoRearm = 0x3EA, + + //Currently, no 0x3EB or 0x3EC + NightSight = 0x3ED, + + DeathStrike, + EvilOmen, + UnknownStandingSwirl, + UnknownKneelingSword, + DivineFury, + EnemyOfOne, + HidingAndOrStealth, + ActiveMeditation, + BloodOathCaster, + BloodOathCurse, + CorpseSkin, + Mindrot, + PainSpike, + Strangle, + GiftOfRenewal, + AttuneWeapon, + Thunderstorm, + EssenceOfWind, + EtherealVoyage, + GiftOfLife, + ArcaneEmpowerment, + MortalStrike, + ReactiveArmor, + Protection, + ArchProtection, + MagicReflection, + Incognito, + Disguised, //* + AnimalForm, + Polymorph, + Invisibility, + Paralyze, + Poison, + Bleed, + Clumsy, + FeebleMind, + Weaken, + Curse, + MassCurse, + Agility, + Cunning, + Strength, + Bless, + Sleep = 1049, + StoneForm = 1050, + SpellPlague = 1051, + GargoyleBerserk = 1052, + GargoyleFly = 1054, + Inspire = 1055, + Invigorate = 1056, + Resilience = 1057, + Perseverance = 1058, + TribulationTarget = 1059, + DespairTarget = 1060, + ArcaneEmpowermentNew = 1061, + MagicFish = 1062, + HitLowerAttack = 1063, + HitLowerDefense = 1064, + HitDualwield = 1065, + Block = 1066, + DefenseMastery = 1067, + Despair = 1068, + HealingSkill = 1069, + SpellFocusing = 1070, + SpellFocusingTarget = 1071, + RageFocusingTarget = 1072, + RageFocusing = 1073, + Warding = 1074, + Tribulation = 1075, + ForceArrow = 1076, + DisarmNew = 1077, + Surge = 1078, + Feint = 1079, + TalonStrike = 1080, + PsychicAttack = 1081, + ConsecrateWeapon = 1082, + EnemyOfOneNew = 1084, + HorrificBeast = 1085, + LichForm = 1086, + VampiricEmbrace = 1087, + CurseWeapon = 1088, + ReaperForm = 1089, + ImmolatingWeapon = 1090, + Enchant = 1091, + HonorableExecution = 1092, + Confidence = 1093, + Evasion = 1094, + CounterAttack = 1095, + LightningStrike = 1096, + MomentumStrike = 1097, + OrangePetals = 1098, + RoseOfTrinsic = 1099, + PoisonResistanceImmunity = 1100, + Veterinary = 1101, + Perfection = 1102, + Honored = 1103, + ManaPhase = 1104, + FanDancerFanFire = 1105, + Rage = 1106, + Webbing = 1107, + MedusaStone = 1108, + DragonSlasherFear = 1109, + AuraOfNausea = 1110, + HowlOfCacophony = 1111, + GazeDespair = 1112, + HiryuPhysicalResistance = 1113, + RuneBeetleCorruption = 1114, + BloodwormAnemia = 1115, + RotwormBloodDisease = 1116, + SkillUseDelay = 1117, + FactionLoss = 1118, + HeatOfBattleStatus = 1119, + CriminalStatus = 1120, + ArmorPierce = 1121, + SplinteringEffect = 1122, + SwingSpeed = 1123, + WraithForm = 1124, + CityTradeDeal = 1126 + } + + public class BuffsDebuffs + { + public int IconNumber { get; set; } + public int Duration { get; set; } + public string ClilocMessage1 { get; set; } + public string ClilocMessage2 { get; set; } + public BuffIcon BuffIcon { get; set; } + public DateTime Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/Core/BuffsTimer.cs b/Core/BuffsTimer.cs new file mode 100644 index 0000000..a6feffd --- /dev/null +++ b/Core/BuffsTimer.cs @@ -0,0 +1,44 @@ +using System; + +namespace Assistant +{ + public class BuffsTimer + { + //private static int m_Count; + private static Timer m_Timer; + + + static BuffsTimer() + { + } + + /*public static int Count + { + get + { + return m_Count; + } + }*/ + + public static bool Running + { + get + { + return m_Timer.Running; + } + } + + public static void Start() + { + + + } + + public static void Stop() + { + + } + + + } +} diff --git a/Core/Config.cs b/Core/Config.cs new file mode 100644 index 0000000..91f1df6 --- /dev/null +++ b/Core/Config.cs @@ -0,0 +1,28 @@ +using System; + +namespace Assistant +{ + internal class Config + { + internal static bool GetBool( string v ) + { + switch ( v ) + { + default: + return true; + case "QueueActions": + return false; + } + } + + internal static int GetInt( string v ) + { + return 0; + } + + internal static string GetString( string v ) + { + return "DEBUG"; + } + } +} \ No newline at end of file diff --git a/Core/ContainerLabels.cs b/Core/ContainerLabels.cs new file mode 100644 index 0000000..3ceeeb4 --- /dev/null +++ b/Core/ContainerLabels.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Assistant.Core +{ + public class ContainerLabels + { + public class ContainerLabel + { + public string Id { get; set; } + public string Type { get; set; } + public string Label { get; set; } + public int Hue { get; set; } + public string Alias { get; set; } + } + + public static Serial LastContainerLabelDisplayed; + + public static List ContainerLabelList = new List(); + + public static void Save(XmlTextWriter xml) + { + foreach (var label in ContainerLabelList) + { + xml.WriteStartElement("containerlabel"); + xml.WriteAttributeString("id", label.Id); + xml.WriteAttributeString("type", label.Type); + xml.WriteAttributeString("label", label.Label); + xml.WriteAttributeString("hue", label.Hue.ToString()); + xml.WriteAttributeString("alias", label.Alias); + xml.WriteEndElement(); + } + } + + public static void Load(XmlElement node) + { + ClearAll(); + + try + { + + foreach (XmlElement el in node.GetElementsByTagName("containerlabel")) + { + ContainerLabel label = new ContainerLabel + { + Id = el.GetAttribute("id"), + Type = el.GetAttribute("type"), + Label = el.GetAttribute("label"), + Hue = Convert.ToInt32(el.GetAttribute("hue")), + Alias = el.GetAttribute("alias") + }; + + ContainerLabelList.Add(label); + } + } + catch + { + } + } + + public static void ClearAll() + { + ContainerLabelList.Clear(); + } + } +} diff --git a/Core/Geometry.cs b/Core/Geometry.cs new file mode 100644 index 0000000..24f952d --- /dev/null +++ b/Core/Geometry.cs @@ -0,0 +1,570 @@ +using System; + +namespace Assistant +{ + public interface IPoint2D + { + int X{ get; } + int Y{ get; } + } + + public interface IPoint3D : IPoint2D + { + int Z{ get; } + } + + public struct Point2D : IPoint2D + { + internal int m_X; + internal int m_Y; + + public static readonly Point2D Zero = new Point2D( 0, 0 ); + public static readonly Point2D MinusOne = new Point2D( -1, -1 ); + + public Point2D( int x, int y ) + { + m_X = x; + m_Y = y; + } + + public Point2D( IPoint2D p ) : this( p.X, p.Y ) + { + } + + public int X + { + get + { + return m_X; + } + set + { + m_X = value; + } + } + + public int Y + { + get + { + return m_Y; + } + set + { + m_Y = value; + } + } + + public override string ToString() + { + return String.Format( "({0}, {1})", m_X, m_Y ); + } + + public override bool Equals( object o ) + { + if ( o == null || !(o is IPoint2D) ) return false; + + IPoint2D p = (IPoint2D)o; + + return m_X == p.X && m_Y == p.Y; + } + + public override int GetHashCode() + { + return m_X ^ m_Y; + } + + public static bool operator == ( Point2D l, Point2D r ) + { + return l.m_X == r.m_X && l.m_Y == r.m_Y; + } + + public static bool operator != ( Point2D l, Point2D r ) + { + return l.m_X != r.m_X || l.m_Y != r.m_Y; + } + + public static bool operator == ( Point2D l, IPoint2D r ) + { + return l.m_X == r.X && l.m_Y == r.Y; + } + + public static bool operator != ( Point2D l, IPoint2D r ) + { + return l.m_X !=r.X || l.m_Y != r.Y; + } + + public static bool operator > ( Point2D l, Point2D r ) + { + return l.m_X > r.m_X && l.m_Y > r.m_Y; + } + + public static bool operator > ( Point2D l, Point3D r ) + { + return l.m_X > r.m_X && l.m_Y > r.m_Y; + } + + public static bool operator > ( Point2D l, IPoint2D r ) + { + return l.m_X > r.X && l.m_Y > r.Y; + } + + public static bool operator < ( Point2D l, Point2D r ) + { + return l.m_X < r.m_X && l.m_Y < r.m_Y; + } + + public static bool operator < ( Point2D l, Point3D r ) + { + return l.m_X < r.m_X && l.m_Y < r.m_Y; + } + + public static bool operator < ( Point2D l, IPoint2D r ) + { + return l.m_X < r.X && l.m_Y < r.Y; + } + + public static bool operator >= ( Point2D l, Point2D r ) + { + return l.m_X >= r.m_X && l.m_Y >= r.m_Y; + } + + public static bool operator >= ( Point2D l, Point3D r ) + { + return l.m_X >= r.m_X && l.m_Y >= r.m_Y; + } + + public static bool operator >= ( Point2D l, IPoint2D r ) + { + return l.m_X >= r.X && l.m_Y >= r.Y; + } + + public static bool operator <= ( Point2D l, Point2D r ) + { + return l.m_X <= r.m_X && l.m_Y <= r.m_Y; + } + + public static bool operator <= ( Point2D l, Point3D r ) + { + return l.m_X <= r.m_X && l.m_Y <= r.m_Y; + } + + public static bool operator <= ( Point2D l, IPoint2D r ) + { + return l.m_X <= r.X && l.m_Y <= r.Y; + } + } + + public struct Point3D : IPoint3D + { + internal int m_X; + internal int m_Y; + internal int m_Z; + + public static readonly Point3D Zero = new Point3D( 0, 0, 0 ); + public static readonly Point3D MinusOne = new Point3D( -1, -1, 0 ); + + public Point3D( int x, int y, int z ) + { + m_X = x; + m_Y = y; + m_Z = z; + } + + public Point3D( IPoint3D p ) : this( p.X, p.Y, p.Z ) + { + } + + public Point3D( IPoint2D p, int z ) : this( p.X, p.Y, z ) + { + } + + public int X + { + get + { + return m_X; + } + set + { + m_X = value; + } + } + + public int Y + { + get + { + return m_Y; + } + set + { + m_Y = value; + } + } + + public int Z + { + get + { + return m_Z; + } + set + { + m_Z = value; + } + } + + public override string ToString() + { + return String.Format( "({0}, {1}, {2})", m_X, m_Y, m_Z ); + } + + public override bool Equals( object o ) + { + if ( o == null || !(o is IPoint3D) ) return false; + + IPoint3D p = (IPoint3D)o; + + return m_X == p.X && m_Y == p.Y && m_Z == p.Z; + } + + public override int GetHashCode() + { + return m_X ^ m_Y ^ m_Z; + } + + public static Point3D Parse( string value ) + { + int start = value.IndexOf( '(' ); + int end = value.IndexOf( ',', start + 1 ); + + string param1 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + start = end; + end = value.IndexOf( ',', start + 1 ); + + string param2 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + start = end; + end = value.IndexOf( ')', start + 1 ); + + string param3 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + return new Point3D( Convert.ToInt32( param1 ), Convert.ToInt32( param2 ), Convert.ToInt16( param3 ) ); + } + + public static bool operator == ( Point3D l, Point3D r ) + { + return l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; + } + + public static bool operator != ( Point3D l, Point3D r ) + { + return l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; + } + + public static bool operator == ( Point3D l, IPoint3D r ) + { + return l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; + } + + public static bool operator != ( Point3D l, IPoint3D r ) + { + return l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z; + } + + public static Point3D operator + ( Point3D l, Point3D r ) + { + return new Point3D( l.m_X+r.m_X, l.m_Y+r.m_Y, l.m_Z+r.m_Z ); + } + + public static Point3D operator - ( Point3D l, Point3D r ) + { + return new Point3D( l.m_X-r.m_X, l.m_Y-r.m_Y, l.m_Z-r.m_Z ); + } + } + + public struct Line2D + { + private Point2D m_Start, m_End; + + public Line2D( IPoint2D start, IPoint2D end ) + { + m_Start = new Point2D( start ); + m_End = new Point2D( end ); + Fix(); + } + + public void Fix() + { + if ( m_Start > m_End ) + { + Point2D temp = m_Start; + m_Start = m_End; + m_End = temp; + } + } + + public Point2D Start + { + get + { + return m_Start; + } + set + { + m_Start = value; + Fix(); + } + } + + public Point2D End + { + get + { + return m_End; + } + set + { + m_End = value; + Fix(); + } + } + + public double Length + { + get + { + int run = m_End.X - m_Start.X; + int rise = m_End.Y - m_Start.Y; + + return Math.Sqrt( run * run + rise * rise ); + } + } + + public override string ToString() + { + return String.Format( "--{0}->{1}--", m_Start, m_End ); + } + + public override bool Equals( object o ) + { + if ( o == null || !(o is Line2D) ) return false; + + Line2D ln = (Line2D)o; + + return m_Start == ln.m_Start && m_End == ln.m_End; + } + + public override int GetHashCode() + { + return m_Start.GetHashCode() ^ (~m_End.GetHashCode()); + } + + public static bool operator == ( Line2D l, Line2D r ) + { + return l.m_Start == r.m_Start && l.m_End == r.m_End; + } + + public static bool operator != ( Line2D l, Line2D r ) + { + return l.m_Start != r.m_Start || l.m_End != r.m_End; + } + + public static bool operator > ( Line2D l, Line2D r ) + { + return l.m_Start > r.m_Start && l.m_End > r.m_End; + } + + public static bool operator < ( Line2D l, Line2D r ) + { + return l.m_Start < r.m_Start && l.m_End < r.m_End; + } + + public static bool operator >= ( Line2D l, Line2D r ) + { + return l.m_Start >= r.m_Start && l.m_End >= r.m_End; + } + + public static bool operator <= ( Line2D l, Line2D r ) + { + return l.m_Start <= r.m_Start && l.m_End <= r.m_End; + } + } + + public struct Rectangle2D + { + private Point2D m_Start; + private Point2D m_End; + + public Rectangle2D( IPoint2D start, IPoint2D end ) + { + m_Start = new Point2D( start ); + m_End = new Point2D( end ); + } + + public Rectangle2D( int x, int y, int width, int height ) + { + m_Start = new Point2D( x, y ); + m_End = new Point2D( x + width, y + height ); + } + + public void Set( int x, int y, int width, int height ) + { + m_Start = new Point2D( x, y ); + m_End = new Point2D( x + width, y + height ); + } + + public static Rectangle2D Parse( string value ) + { + int start = value.IndexOf( '(' ); + int end = value.IndexOf( ',', start + 1 ); + + string param1 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + start = end; + end = value.IndexOf( ',', start + 1 ); + + string param2 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + start = end; + end = value.IndexOf( ',', start + 1 ); + + string param3 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + start = end; + end = value.IndexOf( ')', start + 1 ); + + string param4 = value.Substring( start + 1, end - (start + 1) ).Trim(); + + return new Rectangle2D( Convert.ToInt32( param1 ), Convert.ToInt32( param2 ), Convert.ToInt32( param3 ), Convert.ToInt32( param4 ) ); + } + + public Point2D Start + { + get + { + return m_Start; + } + set + { + m_Start = value; + } + } + + public Point2D End + { + get + { + return m_End; + } + set + { + m_End = value; + } + } + + public int X + { + get + { + return m_Start.m_X; + } + set + { + m_Start.m_X = value; + } + } + + public int Y + { + get + { + return m_Start.m_Y; + } + set + { + m_Start.m_Y = value; + } + } + + public int Width + { + get + { + return m_End.m_X - m_Start.m_X; + } + set + { + m_End.m_X = m_Start.m_X + value; + } + } + + public int Height + { + get + { + return m_End.m_Y - m_Start.m_Y; + } + set + { + m_End.m_Y = m_Start.m_Y + value; + } + } + + public void MakeHold( Rectangle2D r ) + { + if ( r.m_Start.m_X < m_Start.m_X ) + m_Start.m_X = r.m_Start.m_X; + + if ( r.m_Start.m_Y < m_Start.m_Y ) + m_Start.m_Y = r.m_Start.m_Y; + + if ( r.m_End.m_X > m_End.m_X ) + m_End.m_X = r.m_End.m_X; + + if ( r.m_End.m_Y > m_End.m_Y ) + m_End.m_Y = r.m_End.m_Y; + } + + // "test" must be smaller than this rectangle! + public bool Insersects( Rectangle2D test ) + { + Point2D e1 = new Point2D( test.Start.X + test.Width, test.Start.Y ); + Point2D e2 = new Point2D( test.Start.X, test.Start.Y + test.Width ); + + return Contains( test.Start ) || Contains( test.End ) || Contains( e1 ) || Contains( e2 ); + } + + public bool Contains( Rectangle2D test ) + { + Point2D e1 = new Point2D( test.Start.X + test.Width, test.Start.Y ); + Point2D e2 = new Point2D( test.Start.X, test.Start.Y + test.Width ); + + return Contains( test.Start ) && Contains( test.End ) && Contains( e1 ) && Contains( e2 ); + } + + public bool Contains( Point3D p ) + { + return ( m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y ); + //return ( m_Start <= p && m_End > p ); + } + + public bool Contains( Point2D p ) + { + return ( m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y ); + //return ( m_Start <= p && m_End > p ); + } + + public bool Contains( IPoint2D p ) + { + return ( m_Start <= p && m_End > p ); + } + + public override string ToString() + { + return String.Format( "({0}, {1})+({2}, {3})", X, Y, Width, Height ); + } + } +} diff --git a/Core/Item.cs b/Core/Item.cs new file mode 100644 index 0000000..5d95bb4 --- /dev/null +++ b/Core/Item.cs @@ -0,0 +1,817 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public enum Layer : byte + { + Invalid = 0x00, + + FirstValid = 0x01, + + RightHand = 0x01, + LeftHand = 0x02, + Shoes = 0x03, + Pants = 0x04, + Shirt = 0x05, + Head = 0x06, + Gloves = 0x07, + Ring = 0x08, + Unused_x9 = 0x09, + Neck = 0x0A, + Hair = 0x0B, + Waist = 0x0C, + InnerTorso = 0x0D, + Bracelet = 0x0E, + Unused_xF = 0x0F, + FacialHair = 0x10, + MiddleTorso = 0x11, + Earrings = 0x12, + Arms = 0x13, + Cloak = 0x14, + Backpack = 0x15, + OuterTorso = 0x16, + OuterLegs = 0x17, + InnerLegs = 0x18, + + LastUserValid= 0x18, + + Mount = 0x19, + ShopBuy = 0x1A, + ShopResale = 0x1B, + ShopSell = 0x1C, + Bank = 0x1D, + + LastValid = 0x1D + } + + public class Item : UOEntity, IUOEntity + { + + private ItemID m_ItemID; + private ushort m_Amount; + private byte m_Direction; + + private bool m_Visible; + private bool m_Movable; + + private Layer m_Layer; + private string m_Name; + private object m_Parent; + + public IUOEntity Parent { get { return ( IUOEntity)m_Parent; } set { m_Parent = value; } } + + private int m_Price; + private string m_BuyDesc; + private List m_Items; + + private bool m_IsNew; + private bool m_AutoStack; + + private byte[] m_HousePacket; + private int m_HouseRev; + + private byte m_GridNum; + + public override void SaveState( BinaryWriter writer ) + { + base.SaveState (writer); + + writer.Write( (ushort)m_ItemID ); + writer.Write( m_Amount ); + writer.Write( m_Direction ); + writer.Write( (byte)GetPacketFlags() ); + writer.Write( (byte)m_Layer ); + writer.Write( m_Name == null ? "" : m_Name ); + if ( m_Parent is UOEntity ) + writer.Write( (uint)((UOEntity)m_Parent).Serial ); + else if ( m_Parent is Serial ) + writer.Write( (uint)((Serial)m_Parent) ); + else + writer.Write( (uint) 0 ); + + //writer.Write( m_Items.Count ); + //for(int i=0;i(count); + + for (int i=0;i 2 ) + { + m_HouseRev = reader.ReadInt32(); + if ( m_HouseRev != 0 ) + { + int len = reader.ReadUInt16(); + m_HousePacket = reader.ReadBytes( len ); + } + } + else + { + m_HouseRev = 0; + m_HousePacket = null; + } + } + + public override void AfterLoad() + { + m_Items = new List(); + + for (int i=0;i< Serial.Serials.Count; i++) + { + Serial s = Serial.Serials[i]; + if (s.IsItem) + { + Item item = World.FindItem(s); + + if (item != null ) + { + m_Items[i] = item; + } + + Serial.Serials.RemoveAt(i); + i--; + } + } + + UpdateContainer(); + } + + public Item( Serial serial ) : base( serial ) + { + m_Items = new List(); + + m_Visible = true; + m_Movable = true; + + } + + public ItemID ItemID + { + get{ return m_ItemID; } + set{ m_ItemID = value; } + } + + public ushort Amount + { + get{ return m_Amount; } + set{ m_Amount = value; } + } + + public byte Direction + { + get{ return m_Direction; } + set{ m_Direction = value; } + } + + public bool Visible + { + get{ return m_Visible; } + set{ m_Visible = value; } + } + + public bool Movable + { + get{ return m_Movable; } + set{ m_Movable = value; } + } + + public string Name + { + get + { + if ( !string.IsNullOrEmpty(m_Name) ) + { + return m_Name; + } + else + { + return m_ItemID.ToString(); + } + } + set + { + if ( value != null ) + m_Name = value.Trim(); + else + m_Name = null; + } + } + + public string DisplayName + { + get + { + return Ultima.TileData.ItemTable[m_ItemID.Value].Name; + } + } + + public Layer Layer + { + get + { + if ( ( m_Layer < Layer.FirstValid || m_Layer > Layer.LastValid ) && + ( (this.ItemID.ItemData.Flags&Ultima.TileFlag.Wearable) != 0 || + (this.ItemID.ItemData.Flags&Ultima.TileFlag.Armor) != 0 || + (this.ItemID.ItemData.Flags&Ultima.TileFlag.Weapon) != 0 + ) ) + { + m_Layer = (Layer)this.ItemID.ItemData.Quality; + } + return m_Layer; + } + set + { + m_Layer = value; + } + } + + public Item FindItemByID( ItemID id ) + { + return FindItemByID( id, true ); + } + + public Item FindItemByID( ItemID id, bool recurse ) + { + for (int i=0;i ( Container as UOEntity )?.Serial.Value ?? 0; + + public object Container + { + get + { + if ( m_Parent is Serial && UpdateContainer() ) + m_NeedContUpdate.Remove( this ); + return m_Parent; + } + set + { + if ( ( m_Parent != null && m_Parent.Equals( value ) ) + || ( value is Serial && m_Parent is UOEntity && ((UOEntity)m_Parent).Serial == (Serial)value ) + || ( m_Parent is Serial && value is UOEntity && ((UOEntity)value).Serial == (Serial)m_Parent ) ) + { + return; + } + + if ( m_Parent is Mobile ) + ((Mobile)m_Parent).RemoveItem( this ); + else if ( m_Parent is Item ) + ((Item)m_Parent).RemoveItem( this ); + + + + if ( value is Mobile ) + m_Parent = ((Mobile)value).Serial; + else if ( value is Item ) + m_Parent = ((Item)value).Serial; + else + m_Parent = value; + + if ( !UpdateContainer() && m_NeedContUpdate != null ) + m_NeedContUpdate.Add( this ); + } + } + + public bool UpdateContainer() + { + if ( !(m_Parent is Serial) || Deleted ) + return true; + + object o = null; + Serial contSer = (Serial)m_Parent; + if ( contSer.IsItem ) + o = World.FindItem( contSer ); + else if ( contSer.IsMobile ) + o = World.FindMobile( contSer ); + + if ( o == null ) + return false; + + m_Parent = o; + + if ( m_Parent is Item ) + ((Item)m_Parent).AddItem( this ); + else if ( m_Parent is Mobile ) + ((Mobile)m_Parent).AddItem( this ); + + if (World.Player != null && (IsChildOf(World.Player.Backpack) || IsChildOf(World.Player.Quiver))) + { + + + if ( m_IsNew ) + { + if ( m_AutoStack ) + AutoStackResource(); + + if ( IsContainer && ( !IsPouch || !Config.GetBool( "NoSearchPouches" ) ) && Config.GetBool( "AutoSearch" ) ) + { + PacketHandlers.IgnoreGumps.Add( this ); + PlayerData.DoubleClick( this ); + + for (int c=0;c m_NeedContUpdate = new List(); + public static void UpdateContainers() + { + int i = 0; + while ( i < m_NeedContUpdate.Count ) + { + if ( ((Item)m_NeedContUpdate[i]).UpdateContainer() ) + m_NeedContUpdate.RemoveAt( i ); + else + i++; + } + } + + private static List m_AutoStackCache = new List(); + public void AutoStackResource() + { + if ( !IsResource || !Config.GetBool( "AutoStack" ) || m_AutoStackCache.Contains( Serial ) ) + return; + + foreach ( Item check in World.Items.Values ) + { + if ( check.Container == null && check.ItemID == ItemID && check.Hue == Hue && Utility.InRange( World.Player.Position, check.Position, 2 ) ) + { + DragDropManager.DragDrop( this, check ); + m_AutoStackCache.Add( Serial ); + return; + } + } + + DragDropManager.DragDrop( this, World.Player.Position ); + m_AutoStackCache.Add( Serial ); + } + + public object RootContainer + { + get + { + int die = 100; + object cont = this.Container; + while ( cont != null && cont is Item && die-- > 0 ) + cont = ((Item)cont).Container; + + return cont; + } + } + + public bool IsChildOf( object parent ) + { + Serial parentSerial = 0; + if ( parent is Mobile ) + return parent == RootContainer; + else if ( parent is Item ) + parentSerial = ((Item)parent).Serial; + else + return false; + + object check = this; + int die = 100; + while ( check != null && check is Item && die-- > 0 ) + { + if ( ((Item)check).Serial == parentSerial ) + return true; + else + check = ((Item)check).Container; + } + + return false; + } + + public Point3D GetWorldPosition() + { + int die = 100; + object root = this.Container; + while ( root != null && root is Item && ((Item)root).Container != null && die-- > 0 ) + root = ((Item)root).Container; + + if ( root is Item ) + return ((Item)root).Position; + else if ( root is Mobile ) + return ((Mobile)root).Position; + else + return this.Position; + } + + private void AddItem( Item item ) + { + for (int i=0;i y ? x : y; + } + + public void ProcessPacketFlags( byte flags ) + { + m_Visible = ( (flags&0x80) == 0 ); + m_Movable = ( (flags&0x20) != 0 ); + } + + private Timer m_RemoveTimer = null; + + public void RemoveRequest() + { + if ( m_RemoveTimer == null ) + m_RemoveTimer = Timer.DelayedCallback( TimeSpan.FromSeconds( 0.25 ), new TimerCallback( Remove ) ); + else if ( m_RemoveTimer.Running ) + m_RemoveTimer.Stop(); + + m_RemoveTimer.Start(); + } + + public bool CancelRemove() + { + if ( m_RemoveTimer != null && m_RemoveTimer.Running ) + { + m_RemoveTimer.Stop(); + return true; + } + else + { + return false; + } + } + public void ClearContents() + { + List rem = new List( m_Items ); + m_Items.Clear(); + for ( int i = 0; i < rem.Count; i++ ) + ( rem[i] ).Remove(); + } + + public override void Remove() + { + /*if ( IsMulti ) + UOAssist.PostRemoveMulti( this );*/ + + List rem = new List( m_Items ); + m_Items.Clear(); + for (int i=0;i Contains{ get{ return m_Items; } } + + // possibly 4 bit x/y - 16x16? + public byte GridNum + { + get { return m_GridNum; } + set { m_GridNum = value; } + } + + public bool OnGround{ get{ return Container == null; } } + public bool IsContainer + { + get + { + ushort iid = m_ItemID.Value; + return ( m_Items.Count > 0 && !IsCorpse ) || ( iid >= 0x9A8 && iid <= 0x9AC ) || ( iid >= 0x9B0 && iid <= 0x9B2 ) || + ( iid >= 0xA2C && iid <= 0xA53 ) || ( iid >= 0xA97 && iid <= 0xA9E ) || ( iid >= 0xE3C && iid <= 0xE43 ) || + ( iid >= 0xE75 && iid <= 0xE80 && iid != 0xE7B ) || iid == 0x1E80 || iid == 0x1E81 || iid == 0x232A || iid == 0x232B || + iid == 0x2B02 || iid == 0x2B03 || iid == 0x2FB7 || iid == 0x3171 ; + } + } + + public bool IsBagOfSending + { + get + { + return Hue >= 0x0400 && m_ItemID.Value == 0xE76; + } + } + + public bool IsInBank + { + get + { + if ( m_Parent is Item ) + return ((Item)m_Parent).IsInBank; + else if ( m_Parent is Mobile ) + return this.Layer == Layer.Bank; + else + return false; + } + } + + public bool IsNew + { + get{ return m_IsNew; } + set{ m_IsNew = value; } + } + + public bool AutoStack + { + get{ return m_AutoStack; } + set{ m_AutoStack = value; } + } + + public bool IsMulti + { + get { return m_ItemID.Value >= 0x4000; } + } + + public bool IsPouch + { + get { return m_ItemID.Value == 0x0E79; } + } + + public bool IsCorpse + { + get { return m_ItemID.Value == 0x2006 || ( m_ItemID.Value >= 0x0ECA && m_ItemID.Value <= 0x0ED2 ); } + } + + public bool IsDoor + { + get + { + ushort iid = m_ItemID.Value; + return( iid >= 0x0675 && iid <= 0x06F6 ) || ( iid >= 0x0821 && iid <= 0x0875 ) || ( iid >= 0x1FED && iid <= 0x1FFC ) || + ( iid >= 0x241F && iid <= 0x2424 ) || ( iid >= 0x2A05 && iid <= 0x2A1C ); + } + } + + public bool IsResource + { + get + { + ushort iid = m_ItemID.Value; + return ( iid >= 0x19B7 && iid <= 0x19BA ) || // ore + ( iid >= 0x09CC && iid <= 0x09CF ) || // fishes + ( iid >= 0x1BDD && iid <= 0x1BE2 ) || // logs + iid == 0x1779 || // granite / stone + iid == 0x11EA || iid == 0x11EB // sand + ; + } + } + + public bool IsPotion + { + get + { + return ( m_ItemID.Value >= 0x0F06 && m_ItemID.Value <= 0x0F0D ) || + m_ItemID.Value == 0x2790 || m_ItemID.Value == 0x27DB; // Ninja belt (works like a potion) + } + } + + public bool IsVirtueShield + { + get + { + ushort iid = m_ItemID.Value; + return ( iid >= 0x1bc3 && iid <= 0x1bc5 ) ; // virtue shields + } + } + + public bool IsTwoHanded + { + get + { + ushort iid = m_ItemID.Value; + return ( + // everything in layer 2 except shields is 2handed + Layer == Layer.LeftHand && + !( ( iid >= 0x1b72 && iid <= 0x1b7b ) || IsVirtueShield ) // shields + ) || + + // and all of these layer 1 weapons: + ( iid == 0x13fc || iid == 0x13fd ) || // hxbow + ( iid == 0x13AF || iid == 0x13b2 ) || // war axe & bow + ( iid >= 0x0F43 && iid <= 0x0F50 ) || // axes & xbow + ( iid == 0x1438 || iid == 0x1439 ) || // war hammer + ( iid == 0x1442 || iid == 0x1443 ) || // 2handed axe + ( iid == 0x1402 || iid == 0x1403 ) || // short spear + ( iid == 0x26c1 || iid == 0x26cb ) || // aos gay blade + ( iid == 0x26c2 || iid == 0x26cc ) || // aos gay bow + ( iid == 0x26c3 || iid == 0x26cd ) // aos gay xbow + ; + } + } + + public override string ToString() + { + return String.Format( "{0} ({1})", this.Name, this.Serial ); + } + + public int Price + { + get { return m_Price; } + set { m_Price = value; } + } + + public string BuyDesc + { + get { return m_BuyDesc; } + set { m_BuyDesc = value; } + } + + public int HouseRevision + { + get { return m_HouseRev; } + set { m_HouseRev = value; } + } + + public byte[] HousePacket + { + get { return m_HousePacket; } + set { m_HousePacket = value; } + } + + public ushort GraphicID => ItemID; + + public void MakeHousePacket() + { + m_HousePacket = null; + + try + { + // 3 locations... which is right? all of them? wtf? + //"Desktop/{0}/{1}/{2}/Multicache.dat", World.AccountName, World.ShardName, World.OrigPlayerName + //"Desktop/{0}/{1}/{2}/Multicache.dat", World.AccountName, World.ShardName, World.Player.Name ); + //"Desktop/{0}/Multicache.dat", World.AccountName ); + string path = Ultima.Files.GetFilePath(String.Format("Desktop/{0}/{1}/{2}/Multicache.dat", World.AccountName, World.ShardName, World.OrigPlayerName)); + if ( string.IsNullOrEmpty(path) || !File.Exists( path ) ) + return; + + using ( StreamReader reader = new StreamReader( new FileStream( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) ) + { + string line; + reader.ReadLine(); // ver + int skip = 0; + int count = 0; + while ( (line=reader.ReadLine()) != null ) + { + if ( count++ < skip || line == "" || line[0] == ';' ) + continue; + + string[] split = line.Split( ' ', '\t' ); + if ( split.Length <= 0 ) + return; + + skip = 0; + Serial ser = (uint)Utility.ToInt32( split[0], 0 ); + int rev = Utility.ToInt32( split[1], 0 ); + int lines = Utility.ToInt32( split[2], 0 ); + + if ( ser == this.Serial ) + { + m_HouseRev = rev; + MultiTileEntry[] tiles = new MultiTileEntry[lines]; + count = 0; + + Ultima.MultiComponentList mcl = Ultima.Multis.GetComponents( m_ItemID ); + + while ( (line=reader.ReadLine()) != null && count < lines ) + { + split = line.Split( ' ', '\t' ); + + tiles[count] = new MultiTileEntry(); + tiles[count].m_ItemID = (ushort)Utility.ToInt32( split[0], 0 ); + tiles[count].m_OffsetX = (short)(Utility.ToInt32( split[1], 0 ) + mcl.Center.X); + tiles[count].m_OffsetX = (short)(Utility.ToInt32( split[2], 0 ) + mcl.Center.Y); + tiles[count].m_OffsetX = (short)Utility.ToInt32( split[3], 0 ); + + count++; + } + + m_HousePacket = new DesignStateDetailed( Serial, m_HouseRev, mcl.Min.X, mcl.Min.Y, mcl.Max.X, mcl.Max.Y, tiles ).Compile(); + break; + } + else + { + skip = lines; + } + count = 0; + } + } + } + catch// ( Exception e ) + { + //Engine.LogCrash( e ); + } + } + } +} diff --git a/Core/ItemID.cs b/Core/ItemID.cs new file mode 100644 index 0000000..9b855bf --- /dev/null +++ b/Core/ItemID.cs @@ -0,0 +1,100 @@ +using System; + +namespace Assistant +{ + public struct ItemID + { + private ushort m_ID; + + public ItemID( ushort id ) + { + m_ID = id; + } + + public ushort Value + { + get + { + return m_ID; + } + } + public static implicit operator ushort( ItemID a ) + { + return a.m_ID; + } + + public static implicit operator ItemID( ushort a ) + { + return new ItemID( a ); + } + + public override string ToString() + { + try + { + return string.Format( "{0} ({1:X4})", Ultima.TileData.ItemTable[m_ID].Name, m_ID ); + } + catch + { + return String.Format( " ({0:X4})", m_ID ); + } + } + + public Ultima.ItemData ItemData + { + get + { + try + { + return Ultima.TileData.ItemTable[m_ID]; + } + catch + { + return new Ultima.ItemData("", Ultima.TileFlag.None, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + } + } + + public override int GetHashCode() + { + return m_ID; + } + + public override bool Equals( object o ) + { + if ( o == null || !(o is ItemID) ) return false; + + return ((ItemID)o).m_ID == m_ID; + } + + public static bool operator == ( ItemID l, ItemID r ) + { + return l.m_ID == r.m_ID; + } + + public static bool operator != ( ItemID l, ItemID r ) + { + return l.m_ID != r.m_ID; + } + + public static bool operator > ( ItemID l, ItemID r ) + { + return l.m_ID > r.m_ID; + } + + public static bool operator >= ( ItemID l, ItemID r ) + { + return l.m_ID >= r.m_ID; + } + + public static bool operator < ( ItemID l, ItemID r ) + { + return l.m_ID < r.m_ID; + } + + public static bool operator <= ( ItemID l, ItemID r ) + { + return l.m_ID <= r.m_ID; + } + } +} diff --git a/Core/Language.cs b/Core/Language.cs new file mode 100644 index 0000000..a3f811f --- /dev/null +++ b/Core/Language.cs @@ -0,0 +1,34 @@ +using System; + +namespace Assistant +{ + internal class Language + { + public static string CliLocName { get; internal set; } + + internal static string GetString( LocString loc ) + { + throw new NotImplementedException(); + } + + internal static LocString Format( LocString loc, object[] args ) + { + throw new NotImplementedException(); + } + + internal static string GetString( int name ) + { + throw new NotImplementedException(); + } + + internal static string ClilocFormat( int num, string ext_str ) + { + throw new NotImplementedException(); + } + + internal static string GetCliloc( int v ) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Core/LocString.cs b/Core/LocString.cs new file mode 100644 index 0000000..40c3d71 --- /dev/null +++ b/Core/LocString.cs @@ -0,0 +1,7 @@ +namespace Assistant +{ + internal class LocString + { + public static LocString UseOnce { get; internal set; } + } +} \ No newline at end of file diff --git a/Core/Map.cs b/Core/Map.cs new file mode 100644 index 0000000..67bdee2 --- /dev/null +++ b/Core/Map.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections; +using Ultima; + +namespace Assistant +{ + public class MultiTileEntry + { + public ushort m_ItemID; + public short m_OffsetX; + public short m_OffsetY; + public short m_OffsetZ; + } + + public class Map + { + public static Ultima.Map GetMap( int mapNum ) + { + switch ( mapNum ) + { + case 1: return Ultima.Map.Trammel; + case 2: return Ultima.Map.Ilshenar; + case 3: return Ultima.Map.Malas; + case 4: return Ultima.Map.Tokuno; + case 0: + default:return Ultima.Map.Felucca; + } + } + + public static int Parse( string name ) + { + if ( string.IsNullOrEmpty(name) ) + return 0; + + name = name.ToLower(); + + if ( name == "felucca" ) + return 0; + else if ( name == "trammel" ) + return 1; + else if ( name == "ilshenar" ) + return 2; + else if ( name == "malas" ) + return 3; + else if ( name == "samurai" || name == "tokuno" ) + return 4; + else + return 0; + } + + public static HuedTile GetTileNear( int mapNum, int x, int y, int z ) + { + try + { + Ultima.Map map = GetMap( mapNum ); + + HuedTile[] tiles = map.Tiles.GetStaticTiles( x, y ); + if ( tiles != null && tiles.Length > 0 ) + { + for ( int i=0;i= z-5 && tiles[i].Z <= z+5 ) + return tiles[i]; + } + } + } + catch + { + } + + return new HuedTile( 0, 0, (sbyte)z ); + } + + private static void GetAverageZ( Ultima.Map map, int x, int y, ref int z, ref int avg, ref int top ) + { + try + { + int zTop = map.Tiles.GetLandTile( x, y ).Z; + int zLeft = map.Tiles.GetLandTile( x, y + 1 ).Z; + int zRight = map.Tiles.GetLandTile( x + 1, y ).Z; + int zBottom = map.Tiles.GetLandTile( x + 1, y + 1 ).Z; + + z = zTop; + if ( zLeft < z ) + z = zLeft; + if ( zRight < z ) + z = zRight; + if ( zBottom < z ) + z = zBottom; + + top = zTop; + if ( zLeft > top ) + top = zLeft; + if ( zRight > top ) + top = zRight; + if ( zBottom > top ) + top = zBottom; + + if ( Math.Abs( zTop - zBottom ) > Math.Abs( zLeft - zRight) ) + avg = (int)Math.Floor( (zLeft + zRight) / 2.0 ); + else + avg = (int)Math.Floor( (zTop + zBottom) / 2.0 ); + } + catch + { + } + } + + public static sbyte ZTop( int mapNum, int xCheck, int yCheck, int oldZ ) + { + try + { + Ultima.Map map = GetMap( mapNum ); + + Tile landTile = map.Tiles.GetLandTile( xCheck, yCheck ); + int landZ = 0, landCenter = 0, zTop = 0; + + GetAverageZ( map, xCheck, yCheck, ref landZ, ref landCenter, ref zTop ); + + if ( zTop > oldZ ) + oldZ = zTop; + + bool isSet = false; + HuedTile[] staticTiles = map.Tiles.GetStaticTiles( xCheck, yCheck ); + for ( int i = 0; i < staticTiles.Length; ++i ) + { + HuedTile tile = staticTiles[i]; + ItemData id = TileData.ItemTable[tile.ID & 0x3FFF]; + + int calcTop = (tile.Z + id.CalcHeight); + + if ( calcTop <= oldZ+5 && ( !isSet || calcTop > zTop ) && ( (id.Flags & TileFlag.Surface) != 0 || (id.Flags&TileFlag.Wet) != 0 ) ) + { + zTop = calcTop; + isSet = true; + } + } + + return (sbyte)zTop; + } + catch + { + return (sbyte)oldZ; + } + } + } +} + diff --git a/Core/Mobile.cs b/Core/Mobile.cs new file mode 100644 index 0000000..0b5f64c --- /dev/null +++ b/Core/Mobile.cs @@ -0,0 +1,602 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Text; + + +namespace Assistant +{ + [Flags] + public enum Direction : byte + { + North = 0x0, + Right = 0x1, + East = 0x2, + Down = 0x3, + South = 0x4, + Left = 0x5, + West = 0x6, + Up = 0x7, + Mask = 0x7, + Running = 0x80, + ValueMask = 0x87 + } + + //public enum BodyType : byte + //{ + // Empty, + // Monster, + // Sea_Monster, + // Animal, + // Human, + // Equipment + //} + + public class Mobile : UOEntity, IUOEntity + { + private ushort m_Body; + private Direction m_Direction; + private string m_Name; + + public IUOEntity Parent { get { return null; } set { } } + + private byte m_Notoriety; + + private bool m_Visible; + private bool m_Female; + private bool m_Poisoned; + private bool m_Blessed; + private bool m_Warmode; + + //new + private bool m_Unknown; + private bool m_Unknown2; + private bool m_Unknown3; + + private bool m_CanRename; + //end new + + private ushort m_HitsMax, m_Hits; + protected ushort m_StamMax, m_Stam, m_ManaMax, m_Mana; + + private List m_LoadSerials; + private List m_Items = new List(); + + private byte m_Map; + + //private static BodyType[] m_Types; + + //public static void Initialize() + //{ + // using (StreamReader ip = new StreamReader(Path.Combine(Ultima.Files.RootDir, "mobtypes.txt"))) + // { + // m_Types = new BodyType[0x1000]; + + // string line; + + // while ((line = ip.ReadLine()) != null) + // { + // if (line.Length == 0 || line.StartsWith("#")) + // continue; + + // string[] split = line.Split('\t'); + + // BodyType type; + // int bodyID; + + // if (int.TryParse(split[0], out bodyID) && Enum.TryParse(split[1], true, out type) && bodyID >= 0 && + // bodyID < m_Types.Length) + // { + // m_Types[bodyID] = type; + // } + // } + // } + //} + + public override void SaveState(BinaryWriter writer) + { + base.SaveState(writer); + + writer.Write(m_Body); + writer.Write((byte)m_Direction); + writer.Write(m_Name == null ? "" : m_Name); + writer.Write(m_Notoriety); + writer.Write((byte)GetPacketFlags()); + writer.Write(m_HitsMax); + writer.Write(m_Hits); + writer.Write(m_Map); + + writer.Write((int)m_Items.Count); + for (int i = 0; i < m_Items.Count; i++) + writer.Write((uint)(((Item)m_Items[i]).Serial)); + //writer.Write( (int)0 ); + } + + public Mobile(BinaryReader reader, int version) : base(reader, version) + { + m_Body = reader.ReadUInt16(); + m_Direction = (Direction)reader.ReadByte(); + m_Name = reader.ReadString(); + m_Notoriety = reader.ReadByte(); + ProcessPacketFlags(reader.ReadByte()); + m_HitsMax = reader.ReadUInt16(); + m_Hits = reader.ReadUInt16(); + m_Map = reader.ReadByte(); + + int count = reader.ReadInt32(); + m_LoadSerials = new List(); + + for (int i = count - 1; i >= 0; --i) + m_LoadSerials.Add(reader.ReadUInt32()); + } + + public override void AfterLoad() + { + int count = m_LoadSerials.Count; + + for (int i = count - 1; i >= 0; --i) + { + Item it = World.FindItem(m_LoadSerials[i]); + if (it != null) + m_Items.Add(it); + } + m_LoadSerials = null;//per il GC e per liberare RAM + } + + public Mobile(Serial serial) : base(serial) + { + m_Map = World.Player == null ? (byte)0 : World.Player.Map; + m_Visible = true; + + } + + public string Name + { + get + { + if (m_Name == null) + return ""; + else + return m_Name; + } + set + { + if (value != null) + { + string trim = value.Trim(); + if (trim.Length > 0) + m_Name = trim; + } + } + } + + public ushort Body + { + get { return m_Body; } + set { m_Body = value; } + } + + public Direction Direction + { + get { return m_Direction; } + set + { + if (value != m_Direction) + { + var oldDir = m_Direction; + m_Direction = value; + OnDirectionChanging(oldDir); + } + } + } + + public bool Visible + { + get { return m_Visible; } + set { m_Visible = value; } + } + + public bool Poisoned + { + get { return m_Poisoned; } + set { m_Poisoned = value; } + } + + public bool Blessed + { + get { return m_Blessed; } + set { m_Blessed = value; } + } + + public bool IsGhost + { + get + { + return m_Body == 402 + || m_Body == 403 + || m_Body == 607 + || m_Body == 608 + || m_Body == 970; + } + } + + public bool IsHuman + { + get + { + return m_Body >= 0 + && (m_Body == 400 + || m_Body == 401 + || m_Body == 402 + || m_Body == 403 + || m_Body == 605 + || m_Body == 606 + || m_Body == 607 + || m_Body == 608 + || m_Body == 970); //player ghost + } + } + + public bool IsMonster + { + get + { + return !IsHuman; + } + } + + //new + public bool Unknown + { + get { return m_Unknown; } + set { m_Unknown = value; } + } + public bool Unknown2 + { + get { return m_Unknown2; } + set { m_Unknown2 = value; } + } + public bool Unknown3 + { + get { return m_Unknown3; } + set { m_Unknown3 = value; } + } + public bool CanRename //A pet! (where the health bar is open, we can add this to an arraylist of mobiles... + { + get { return m_CanRename; } + set { m_CanRename = value; } + } + //end new + + public bool Warmode + { + get { return m_Warmode; } + set { m_Warmode = value; } + } + + public bool Female + { + get { return m_Female; } + set { m_Female = value; } + } + + public byte Notoriety + { + get { return m_Notoriety; } + set + { + if (value != Notoriety) + { + OnNotoChange(m_Notoriety, value); + m_Notoriety = value; + } + } + } + + protected virtual void OnNotoChange(byte old, byte cur) + { + } + + // grey, blue, green, 'canbeattacked' + private static uint[] m_NotoHues = new uint[8] + { + // hue color #30 + 0x000000, // black unused 0 + 0x30d0e0, // blue 0x0059 1 + 0x60e000, // green 0x003F 2 + 0x9090b2, // greyish 0x03b2 3 + 0x909090, // grey " 4 + 0xd88038, // orange 0x0090 5 + 0xb01000, // red 0x0022 6 + 0xe0e000 // yellow 0x0035 7 + }; + + private static int[] m_NotoHuesInt = new int[8] + { + 1, // black unused 0 + 0x059, // blue 0x0059 1 + 0x03F, // green 0x003F 2 + 0x3B2, // greyish 0x03b2 3 + 0x3B2, // grey " 4 + 0x090, // orange 0x0090 5 + 0x022, // red 0x0022 6 + 0x035, // yellow 0x0035 7 + }; + + public uint GetNotorietyColor() + { + if (m_Notoriety < 0 || m_Notoriety >= m_NotoHues.Length) + return m_NotoHues[0]; + else + return m_NotoHues[m_Notoriety]; + } + + public int GetNotorietyColorInt() + { + if (m_Notoriety < 0 || m_Notoriety >= m_NotoHues.Length) + return m_NotoHuesInt[0]; + else + return m_NotoHuesInt[m_Notoriety]; + } + + public byte GetStatusCode() + { + if (m_Poisoned) + return 1; + else + return 0; + } + + public ushort HitsMax + { + get { return m_HitsMax; } + set { m_HitsMax = value; } + } + + public ushort Hits + { + get { return m_Hits; } + set { m_Hits = value; } + } + + public ushort Stam + { + get { return m_Stam; } + set { m_Stam = value; } + } + + public ushort StamMax + { + get { return m_StamMax; } + set { m_StamMax = value; } + } + + public ushort Mana + { + get { return m_Mana; } + set { m_Mana = value; } + } + + public ushort ManaMax + { + get { return m_ManaMax; } + set { m_ManaMax = value; } + } + + + public byte Map + { + get { return m_Map; } + set + { + if (m_Map != value) + { + OnMapChange(m_Map, value); + m_Map = value; + } + } + } + + public virtual void OnMapChange(byte old, byte cur) + { + } + + public void AddItem(Item item) + { + m_Items.Add(item); + } + + public void RemoveItem(Item item) + { + m_Items.Remove(item); + } + + public override void Remove() + { + List rem = new List(m_Items); + m_Items.Clear(); + + for (int i = 0; i < rem.Count; i++) + rem[i].Remove(); + + if (!InParty) + { + base.Remove(); + World.RemoveMobile(this); + } + else + { + Visible = false; + } + } + + public bool InParty + { + get + { + return PacketHandlers.Party.Contains(this.Serial); + } + } + + public Item GetItemOnLayer(Layer layer) + { + for (int i = 0; i < m_Items.Count; i++) + { + Item item = (Item)m_Items[i]; + if (item.Layer == layer) + return item; + } + return null; + } + + public Item Backpack + { + get + { + return GetItemOnLayer(Layer.Backpack); + } + } + + public Item Quiver + { + get + { + Item item = GetItemOnLayer(Layer.Cloak); + + if (item != null && item.IsContainer) + return item; + else + return null; + } + } + + public Item FindItemByID(ItemID id) + { + for (int i = 0; i < Contains.Count; i++) + { + Item item = (Item)Contains[i]; + if (item.ItemID == id) + return item; + } + return null; + } + + public override void OnPositionChanging(Point3D oldPos) + { + + base.OnPositionChanging(oldPos); + } + + public virtual void OnDirectionChanging(Direction oldDir) + { + } + + public int GetPacketFlags() + { + int flags = 0x0; + + if (m_Female) + flags |= 0x02; + + if (m_Poisoned) + flags |= 0x04; + + if (m_Blessed) + flags |= 0x08; + + if (m_Warmode) + flags |= 0x40; + + if (!m_Visible) + flags |= 0x80; + + if (m_Unknown) + flags |= 0x01; + + if (m_Unknown2) + flags |= 0x10; + + if (m_Unknown3) + flags |= 0x20; + + return flags; + } + + public void ProcessPacketFlags(byte flags) + { + if (!PacketHandlers.UseNewStatus) + m_Poisoned = (flags & 0x04) != 0; + + m_Unknown = (flags & 0x01) != 0; //new + m_Female = (flags & 0x02) != 0; + m_Blessed = (flags & 0x08) != 0; + m_Unknown2 = (flags & 0x10) != 0; //new + m_Unknown3 = (flags & 0x10) != 0; //new + m_Warmode = (flags & 0x40) != 0; + m_Visible = (flags & 0x80) == 0; + + + } + + public List Contains { get { return m_Items; } } + + internal void OverheadMessageFrom(int hue, string from, string format, params object[] args) + { + OverheadMessageFrom(hue, from, String.Format(format, args)); + } + + internal void OverheadMessageFrom(int hue, string from, string text) + { + if (Config.GetInt("OverheadStyle") == 0) + { + ClientCommunication.SendToClient(new AsciiMessage(Serial, m_Body, MessageType.Regular, hue, 3, Language.CliLocName, text)); + } + else + { + ClientCommunication.SendToClient(new UnicodeMessage(Serial, m_Body, MessageType.Regular, hue, 3, Language.CliLocName, from, text)); + } + } + + internal void OverheadMessage(string text) + { + OverheadMessage(Config.GetInt("SysColor"), text); + } + + internal void OverheadMessage(string format, params object[] args) + { + OverheadMessage(Config.GetInt("SysColor"), String.Format(format, args)); + } + + internal void OverheadMessage(int hue, string format, params object[] args) + { + OverheadMessage(hue, String.Format(format, args)); + } + + internal void OverheadMessage(int hue, string text) + { + OverheadMessageFrom(hue, "Razor", text); + } + + internal void OverheadMessage(LocString str) + { + OverheadMessage(Language.GetString(str)); + } + + internal void OverheadMessage(LocString str, params object[] args) + { + OverheadMessage(Language.Format(str, args)); + } + + private Point2D m_ButtonPoint = Point2D.Zero; + internal Point2D ButtonPoint + { + get { return m_ButtonPoint; } + set { m_ButtonPoint = value; } + } + + public ushort GraphicID { get => Body; } + } +} + + diff --git a/Core/ObjectPropertyList.cs b/Core/ObjectPropertyList.cs new file mode 100644 index 0000000..0a2c98e --- /dev/null +++ b/Core/ObjectPropertyList.cs @@ -0,0 +1,403 @@ +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public class ObjectPropertyList + { + private class OPLEntry + { + public int Number = 0; + public string Args = null; + + public OPLEntry( int num ) : this( num, null ) + { + } + + public OPLEntry( int num, string args ) + { + Number = num; + Args = args; + } + } + + private List m_StringNums = new List(); + + private int m_Hash = 0; + private List m_Content = new List(); + + private int m_CustomHash = 0; + private List m_CustomContent = new List(); + + + private UOEntity m_Owner = null; + + public int Hash + { + get { return m_Hash ^ m_CustomHash; } + set { m_Hash = value; } + } + + public int ServerHash { get { return m_Hash; } } + + public bool Customized { get { return m_CustomHash != 0; } } + + public ObjectPropertyList( UOEntity owner ) + { + m_Owner = owner; + + m_StringNums.AddRange( m_DefaultStringNums ); + } + + public UOEntity Owner { get { return m_Owner; } } + + public void Read( PacketReader p ) + { + m_Content.Clear(); + + p.Seek( 5, System.IO.SeekOrigin.Begin ); // seek to packet data + + p.ReadUInt32(); // serial + p.ReadByte(); // 0 + p.ReadByte(); // 0 + m_Hash = p.ReadInt32(); + + m_StringNums.Clear(); + m_StringNums.AddRange( m_DefaultStringNums ); + + while ( p.Position < p.Length ) + { + int num = p.ReadInt32(); + if ( num == 0 ) + break; + + m_StringNums.Remove( num ); + + short bytes = p.ReadInt16(); + string args = null; + if ( bytes > 0 ) + args = p.ReadUnicodeStringBE( bytes >> 1 ); + + m_Content.Add( new OPLEntry( num, args ) ); + } + + for(int i=0;i> 26) & 0x3F; + } + + public void Add( int number, string arguments ) + { + if ( number == 0 ) + return; + + AddHash( number ); + m_CustomContent.Add( new OPLEntry( number, arguments ) ); + } + + public void Add( int number, string format, object arg0 ) + { + Add( number, String.Format( format, arg0 ) ); + } + + public void Add( int number, string format, object arg0, object arg1 ) + { + Add( number, String.Format( format, arg0, arg1 ) ); + } + + public void Add( int number, string format, object arg0, object arg1, object arg2 ) + { + Add( number, String.Format( format, arg0, arg1, arg2 ) ); + } + + public void Add( int number, string format, params object[] args ) + { + Add( number, String.Format( format, args ) ); + } + + private static int[] m_DefaultStringNums = new int[] + { + 1042971, // ~1_NOTHING~ + 1070722, // ~1_NOTHING~ + 1063483, // ~1_MATERIAL~ ~2_ITEMNAME~ + 1076228, // ~1_DUMMY~ ~2_DUMMY~ + 1060847, // ~1_val~ ~2_val~ + 1050039 // ~1_NUMBER~ ~2_ITEMNAME~ + // these are ugly: + //1062613, // "~1_NAME~" (orange) + //1049644, // [~1_stuff~] + }; + + private int GetStringNumber() + { + if ( m_StringNums.Count > 0 ) + { + int num = (int)m_StringNums[0]; + m_StringNums.RemoveAt( 0 ); + return num; + } + else + { + return 1049644; + } + } + + private const string RazorHTMLFormat = "
{0}
"; + + public void Add( string text ) + { + Add( GetStringNumber(), String.Format( RazorHTMLFormat, text ) ); + } + + public void Add( string format, string arg0 ) + { + Add( GetStringNumber(), String.Format( format, arg0 ) ); + } + + public void Add( string format, string arg0, string arg1 ) + { + Add( GetStringNumber(), String.Format( format, arg0, arg1 ) ); + } + + public void Add( string format, string arg0, string arg1, string arg2 ) + { + Add( GetStringNumber(), String.Format( format, arg0, arg1, arg2 ) ); + } + + public void Add( string format, params object[] args ) + { + Add( GetStringNumber(), String.Format( format, args ) ); + } + + public bool Remove( int number ) + { + for ( int i = 0; i < m_Content.Count; i++ ) + { + OPLEntry ent = (OPLEntry)m_Content[i]; + if ( ent == null ) + continue; + + if ( ent.Number == number ) + { + for (int s=0;s m_Buffer.Length ) + m_Buffer = new byte[byteCount]; + + byteCount = Encoding.Unicode.GetBytes( ent.Args, 0, ent.Args.Length, m_Buffer, 0 ); + + p.Write( (short) byteCount ); + p.Write( m_Buffer, 0, byteCount ); + } + else + { + p.Write( (short) 0 ); + } + } + } + + foreach ( OPLEntry ent in m_CustomContent ) + { + try + { + if ( ent != null && ent.Number != 0 ) + { + string arguments = ent.Args; + + p.Write( (int)ent.Number ); + + if ( string.IsNullOrEmpty(arguments) ) + arguments = " "; + arguments += "\t "; + + if ( !string.IsNullOrEmpty(arguments) ) + { + int byteCount = Encoding.Unicode.GetByteCount( arguments ); + + if ( byteCount > m_Buffer.Length ) + m_Buffer = new byte[byteCount]; + + byteCount = Encoding.Unicode.GetBytes( arguments, 0, arguments.Length, m_Buffer, 0 ); + + p.Write( (short) byteCount ); + p.Write( m_Buffer, 0, byteCount ); + } + else + { + p.Write( (short) 0 ); + } + } + } + catch + { + } + } + + p.Write( (int) 0 ); + + return p; + } + } + + public class OPLInfo : Packet + { + public OPLInfo( Serial ser, int hash ) : base( 0xDC, 9 ) + { + Write( (uint) ser ); + Write( (int) hash ); + } + } +} diff --git a/Core/OverheadMessages.cs b/Core/OverheadMessages.cs new file mode 100644 index 0000000..61141dc --- /dev/null +++ b/Core/OverheadMessages.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Assistant.Core +{ + public class OverheadMessages + { + public class OverheadMessage + { + public string SearchMessage { get; set; } + public string MessageOverhead { get; set; } + } + + public static List OverheadMessageList = new List(); + + public static void Save(XmlTextWriter xml) + { + foreach (var message in OverheadMessageList) + { + xml.WriteStartElement("overheadmessage"); + xml.WriteAttributeString("searchtext", message.SearchMessage); + xml.WriteAttributeString("message", (message.MessageOverhead)); + xml.WriteEndElement(); + } + } + + public static void Load(XmlElement node) + { + ClearAll(); + + try + { + + foreach (XmlElement el in node.GetElementsByTagName("overheadmessage")) + { + OverheadMessage overheadMessage = new OverheadMessage + { + MessageOverhead = el.GetAttribute("message"), + SearchMessage = el.GetAttribute("searchtext") + }; + + OverheadMessageList.Add(overheadMessage); + } + } + catch + { + } + } + + public static void ClearAll() + { + OverheadMessageList.Clear(); + } + } +} diff --git a/Core/Player.cs b/Core/Player.cs new file mode 100644 index 0000000..a9fca27 --- /dev/null +++ b/Core/Player.cs @@ -0,0 +1,955 @@ +using System; +using System.IO; +using System.Reflection; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Assistant.Core; + +using Ultima; + +namespace Assistant +{ + public class GumpResponseAction + { + private int m_ButtonID; + public int Button => m_ButtonID; + private int[] m_Switches; + private GumpTextEntry[] m_TextEntries; + + public GumpResponseAction( string[] args ) + { + m_ButtonID = Convert.ToInt32( args[1] ); + m_Switches = new int[Convert.ToInt32( args[2] )]; + for ( int i = 0; i < m_Switches.Length; i++ ) + m_Switches[i] = Convert.ToInt32( args[3 + i] ); + m_TextEntries = new GumpTextEntry[Convert.ToInt32( args[3 + m_Switches.Length] )]; + for ( int i = 0; i < m_TextEntries.Length; i++ ) + { + string[] split = args[4 + m_Switches.Length + i].Split( '&' ); + m_TextEntries[i].EntryID = Convert.ToUInt16( split[0] ); + m_TextEntries[i].Text = split[1]; + } + } + + public GumpResponseAction( int button, int[] switches, GumpTextEntry[] entries ) + { + m_ButtonID = button; + m_Switches = switches; + m_TextEntries = entries; + } + + public bool Perform() + { + ClientCommunication.SendToClient( new CloseGump( World.Player.CurrentGumpI ) ); + ClientCommunication.SendToServer( new GumpResponse( World.Player.CurrentGumpS, World.Player.CurrentGumpI, m_ButtonID, m_Switches, m_TextEntries ) ); + World.Player.HasGump = false; + return true; + } + + + private void UseLastResponse( object[] args ) + { + m_ButtonID = World.Player.LastGumpResponseAction.m_ButtonID; + m_Switches = World.Player.LastGumpResponseAction.m_Switches; + m_TextEntries = World.Player.LastGumpResponseAction.m_TextEntries; + + World.Player.SendMessage( MsgLevel.Force, "Set GumpResponse to last response" ); + + } + } + + public enum LockType : byte + { + Up = 0, + Down = 1, + Locked = 2 + } + + public enum MsgLevel + { + Debug = 0, + Info = 0, + Warning = 1, + Error = 2, + Force = 3 + } + + public class Skill + { + public static int Count = 55; + + private LockType m_Lock; + private ushort m_Value; + private ushort m_Base; + private ushort m_Cap; + private short m_Delta; + private int m_Idx; + + public Skill(int idx) + { + m_Idx = idx; + } + + public int Index { get { return m_Idx; } } + + public LockType Lock + { + get { return m_Lock; } + set { m_Lock = value; } + } + + public ushort FixedValue + { + get { return m_Value; } + set { m_Value = value; } + } + + public ushort FixedBase + { + get { return m_Base; } + set + { + m_Delta += (short)(value - m_Base); + m_Base = value; + } + } + + public ushort FixedCap + { + get { return m_Cap; } + set { m_Cap = value; } + } + + public double Value + { + get { return m_Value / 10.0; } + set { m_Value = (ushort)(value * 10.0); } + } + + public double Base + { + get { return m_Base / 10.0; } + set { m_Base = (ushort)(value * 10.0); } + } + + public double Cap + { + get { return m_Cap / 10.0; } + set { m_Cap = (ushort)(value * 10.0); } + } + + public double Delta + { + get { return m_Delta / 10.0; } + set { m_Delta = (short)(value * 10); } + } + } + + public enum SkillName + { + Alchemy = 0, + Anatomy = 1, + AnimalLore = 2, + ItemID = 3, + ArmsLore = 4, + Parry = 5, + Begging = 6, + Blacksmith = 7, + Fletching = 8, + Peacemaking = 9, + Camping = 10, + Carpentry = 11, + Cartography = 12, + Cooking = 13, + DetectHidden = 14, + Discordance = 15, + EvalInt = 16, + Healing = 17, + Fishing = 18, + Forensics = 19, + Herding = 20, + Hiding = 21, + Provocation = 22, + Inscribe = 23, + Lockpicking = 24, + Magery = 25, + MagicResist = 26, + Tactics = 27, + Snooping = 28, + Musicianship = 29, + Poisoning = 30, + Archery = 31, + SpiritSpeak = 32, + Stealing = 33, + Tailoring = 34, + AnimalTaming = 35, + TasteID = 36, + Tinkering = 37, + Tracking = 38, + Veterinary = 39, + Swords = 40, + Macing = 41, + Fencing = 42, + Wrestling = 43, + Lumberjacking = 44, + Mining = 45, + Meditation = 46, + Stealth = 47, + RemoveTrap = 48, + Necromancy = 49, + Focus = 50, + Chivalry = 51, + Bushido = 52, + Ninjitsu = 53, + SpellWeaving = 54 + } + + public enum MaleSounds + { + Ah = 0x41A, + Ahha = 0x41B, + Applaud = 0x41C, + BlowNose = 0x41D, + Burp = 0x41E, + Cheer = 0x41F, + ClearThroat = 0x420, + Cough = 0x421, + CoughBS = 0x422, + Cry = 0x423, + Fart = 0x429, + Gasp = 0x42A, + Giggle = 0x42B, + Groan = 0x42C, + Growl = 0x42D, + Hey = 0x42E, + Hiccup = 0x42F, + Huh = 0x430, + Kiss = 0x431, + Laugh = 0x432, + No = 0x433, + Oh = 0x434, + Oomph1 = 0x435, + Oomph2 = 0x436, + Oomph3 = 0x437, + Oomph4 = 0x438, + Oomph5 = 0x439, + Oomph6 = 0x43A, + Oomph7 = 0x43B, + Oomph8 = 0x43C, + Oomph9 = 0x43D, + Oooh = 0x43E, + Oops = 0x43F, + Puke = 0x440, + Scream = 0x441, + Shush = 0x442, + Sigh = 0x443, + Sneeze = 0x444, + Sniff = 0x445, + Snore = 0x446, + Spit = 0x447, + Whistle = 0x448, + Yawn = 0x449, + Yea = 0x44A, + Yell = 0x44B, + } + + public enum FemaleSounds + { + Ah = 0x30B, + Ahha = 0x30C, + Applaud = 0x30D, + BlowNose = 0x30E, + Burp = 0x30F, + Cheer = 0x310, + ClearThroat = 0x311, + Cough = 0x312, + CoughBS = 0x313, + Cry = 0x314, + Fart = 0x319, + Gasp = 0x31A, + Giggle = 0x31B, + Groan = 0x31C, + Growl = 0x31D, + Hey = 0x31E, + Hiccup = 0x31F, + Huh = 0x320, + Kiss = 0x321, + Laugh = 0x322, + No = 0x323, + Oh = 0x324, + Oomph1 = 0x325, + Oomph2 = 0x326, + Oomph3 = 0x327, + Oomph4 = 0x328, + Oomph5 = 0x329, + Oomph6 = 0x32A, + Oomph7 = 0x32B, + Oooh = 0x32C, + Oops = 0x32D, + Puke = 0x32E, + Scream = 0x32F, + Shush = 0x330, + Sigh = 0x331, + Sneeze = 0x332, + Sniff = 0x333, + Snore = 0x334, + Spit = 0x335, + Whistle = 0x336, + Yawn = 0x337, + Yea = 0x338, + Yell = 0x339, + } + + public class PlayerData : Mobile + { + public int VisRange = 18; + public int MultiVisRange { get { return VisRange + 5; } } + + private int m_MaxWeight = -1; + + private short m_FireResist, m_ColdResist, m_PoisonResist, m_EnergyResist, m_Luck; + private ushort m_DamageMin, m_DamageMax; + + private ushort m_Str, m_Dex, m_Int; + private LockType m_StrLock, m_DexLock, m_IntLock; + private uint m_Gold; + private ushort m_Weight; + private Skill[] m_Skills; + private ushort m_AR; + private ushort m_StatCap; + private byte m_Followers; + private byte m_FollowersMax; + private int m_Tithe; + private sbyte m_LocalLight; + private byte m_GlobalLight; + private ushort m_Features; + private byte m_Season; + private byte m_DefaultSeason; + private int[] m_MapPatches = new int[10]; + + + private bool m_SkillsSent; + private DateTime m_CriminalStart = DateTime.MinValue; + + internal List m_BuffsDebuffs = new List(); + internal List BuffsDebuffs { get { return m_BuffsDebuffs; } } + + private List m_OpenedCorpses = new List(); + public List OpenedCorpses { get { return m_OpenedCorpses; } } + + public Direction GetDirectionTo( int x, int y ) + { + int dx = Position.m_X - x; + int dy = Position.m_Y - y; + + int rx = ( dx - dy ) * 44; + int ry = ( dx + dy ) * 44; + + int ax = Math.Abs( rx ); + int ay = Math.Abs( ry ); + + Direction ret; + + if ( ( ( ay >> 1 ) - ax ) >= 0 ) + ret = ( ry > 0 ) ? Direction.Up : Direction.Down; + else if ( ( ( ax >> 1 ) - ay ) >= 0 ) + ret = ( rx > 0 ) ? Direction.Left : Direction.Right; + else if ( rx >= 0 && ry >= 0 ) + ret = Direction.West; + else if ( rx >= 0 && ry < 0 ) + ret = Direction.South; + else if ( rx < 0 && ry < 0 ) + ret = Direction.East; + else + ret = Direction.North; + + return ret; + } + + public Direction GetDirectionTo( Point2D p ) + { + return GetDirectionTo( p.m_X, p.m_Y ); + } + + public Direction GetDirectionTo( Point3D p ) + { + return GetDirectionTo( p.m_X, p.m_Y ); + } + + public Direction GetDirectionTo( IPoint2D p ) + { + if ( p == null ) + return Direction.North; + + return GetDirectionTo( p.X, p.Y ); + } + public override void SaveState(BinaryWriter writer) + { + base.SaveState(writer); + + writer.Write(m_Str); + writer.Write(m_Dex); + writer.Write(m_Int); + writer.Write(m_StamMax); + writer.Write(m_Stam); + writer.Write(m_ManaMax); + writer.Write(m_Mana); + writer.Write((byte)m_StrLock); + writer.Write((byte)m_DexLock); + writer.Write((byte)m_IntLock); + writer.Write(m_Gold); + writer.Write(m_Weight); + + writer.Write((byte)Skill.Count); + for (int i = 0; i < Skill.Count; i++) + { + writer.Write(m_Skills[i].FixedBase); + writer.Write(m_Skills[i].FixedCap); + writer.Write(m_Skills[i].FixedValue); + writer.Write((byte)m_Skills[i].Lock); + } + + writer.Write(m_AR); + writer.Write(m_StatCap); + writer.Write(m_Followers); + writer.Write(m_FollowersMax); + writer.Write(m_Tithe); + + writer.Write(m_LocalLight); + writer.Write(m_GlobalLight); + writer.Write(m_Features); + writer.Write(m_Season); + + writer.Write((byte)m_MapPatches.Length); + for (int i = 0; i < m_MapPatches.Length; i++) + writer.Write((int)m_MapPatches[i]); + } + + public PlayerData(BinaryReader reader, int version) : base(reader, version) + { + int c; + m_Str = reader.ReadUInt16(); + m_Dex = reader.ReadUInt16(); + m_Int = reader.ReadUInt16(); + m_StamMax = reader.ReadUInt16(); + m_Stam = reader.ReadUInt16(); + m_ManaMax = reader.ReadUInt16(); + m_Mana = reader.ReadUInt16(); + m_StrLock = (LockType)reader.ReadByte(); + m_DexLock = (LockType)reader.ReadByte(); + m_IntLock = (LockType)reader.ReadByte(); + m_Gold = reader.ReadUInt32(); + m_Weight = reader.ReadUInt16(); + + if (version >= 4) + { + Skill.Count = c = reader.ReadByte(); + } + else if (version == 3) + { + long skillStart = reader.BaseStream.Position; + c = 0; + reader.BaseStream.Seek(7 * 49, SeekOrigin.Current); + for (int i = 48; i < 60; i++) + { + ushort Base, Cap, Val; + byte Lock; + + Base = reader.ReadUInt16(); + Cap = reader.ReadUInt16(); + Val = reader.ReadUInt16(); + Lock = reader.ReadByte(); + + if (Base > 2000 || Cap > 2000 || Val > 2000 || Lock > 2) + { + c = i; + break; + } + } + + if (c == 0) + c = 52; + else if (c > 54) + c = 54; + + Skill.Count = c; + + reader.BaseStream.Seek(skillStart, SeekOrigin.Begin); + } + else + { + Skill.Count = c = 52; + } + + m_Skills = new Skill[c]; + for (int i = 0; i < c; i++) + { + m_Skills[i] = new Skill(i); + m_Skills[i].FixedBase = reader.ReadUInt16(); + m_Skills[i].FixedCap = reader.ReadUInt16(); + m_Skills[i].FixedValue = reader.ReadUInt16(); + m_Skills[i].Lock = (LockType)reader.ReadByte(); + } + + m_AR = reader.ReadUInt16(); + m_StatCap = reader.ReadUInt16(); + m_Followers = reader.ReadByte(); + m_FollowersMax = reader.ReadByte(); + m_Tithe = reader.ReadInt32(); + + m_LocalLight = reader.ReadSByte(); + m_GlobalLight = reader.ReadByte(); + m_Features = reader.ReadUInt16(); + m_Season = reader.ReadByte(); + + if (version >= 4) + c = reader.ReadByte(); + else + c = 8; + m_MapPatches = new int[c]; + for (int i = 0; i < c; i++) + m_MapPatches[i] = reader.ReadInt32(); + } + + public PlayerData(Serial serial) : base(serial) + { + m_Skills = new Skill[Skill.Count]; + for (int i = 0; i < m_Skills.Length; i++) + m_Skills[i] = new Skill(i); + } + + public ushort Str + { + get { return m_Str; } + set { m_Str = value; } + } + + public ushort Dex + { + get { return m_Dex; } + set { m_Dex = value; } + } + + public ushort Int + { + get { return m_Int; } + set { m_Int = value; } + } + + public uint Gold + { + get { return m_Gold; } + set { m_Gold = value; } + } + + public ushort Weight + { + get { return m_Weight; } + set { m_Weight = value; } + } + + public ushort MaxWeight + { + get + { + if (m_MaxWeight == -1) + return (ushort)((m_Str * 3.5) + 40); + else + return (ushort)m_MaxWeight; + } + set + { + m_MaxWeight = value; + } + } + + public short FireResistance + { + get { return m_FireResist; } + set { m_FireResist = value; } + } + + public short ColdResistance + { + get { return m_ColdResist; } + set { m_ColdResist = value; } + } + + public short PoisonResistance + { + get { return m_PoisonResist; } + set { m_PoisonResist = value; } + } + + public short EnergyResistance + { + get { return m_EnergyResist; } + set { m_EnergyResist = value; } + } + + public short Luck + { + get { return m_Luck; } + set { m_Luck = value; } + } + + public ushort DamageMin + { + get { return m_DamageMin; } + set { m_DamageMin = value; } + } + + public ushort DamageMax + { + get { return m_DamageMax; } + set { m_DamageMax = value; } + } + + public LockType StrLock + { + get { return m_StrLock; } + set { m_StrLock = value; } + } + + public LockType DexLock + { + get { return m_DexLock; } + set { m_DexLock = value; } + } + + public LockType IntLock + { + get { return m_IntLock; } + set { m_IntLock = value; } + } + + public ushort StatCap + { + get { return m_StatCap; } + set { m_StatCap = value; } + } + + public ushort AR + { + get { return m_AR; } + set { m_AR = value; } + } + + public byte Followers + { + get { return m_Followers; } + set { m_Followers = value; } + } + + public byte FollowersMax + { + get { return m_FollowersMax; } + set { m_FollowersMax = value; } + } + + public int Tithe + { + get { return m_Tithe; } + set { m_Tithe = value; } + } + + public Skill[] Skills { get { return m_Skills; } } + + public bool SkillsSent + { + get { return m_SkillsSent; } + set { m_SkillsSent = value; } + } + + + private void AutoOpenDoors() + { + if (Body != 0x03DB && + !IsGhost && + ((int)(Direction & Direction.Mask)) % 2 == 0 && + Config.GetBool("AutoOpenDoors")) + { + int x = Position.X, y = Position.Y, z = Position.Z; + + /* Check if one more tile in the direction we just moved is a door */ + Utility.Offset(Direction, ref x, ref y); + + foreach (Item i in World.Items.Values) + { + if (i.IsDoor && i.Position.X == x && i.Position.Y == y && i.Position.Z - 15 <= z && i.Position.Z + 15 >= z) + { + ClientCommunication.SendToServer(new OpenDoorMacro()); + break; + } + } + } + } + + + public override void OnPositionChanging(Point3D oldPos) + { + + AutoOpenDoors(); + + List mlist = new List(World.Mobiles.Values); + for (int i = 0; i < mlist.Count; i++) + { + Mobile m = mlist[i]; + if (m != this) + { + if (!Utility.InRange(m.Position, Position, VisRange)) + m.Remove(); + else + Targeting.CheckLastTargetRange(m); + } + } + + mlist = null; + + + List ilist = new List(World.Items.Values); + for (int i = 0; i < ilist.Count; i++) + { + Item item = ilist[i]; + if (item.Deleted || item.Container != null) + continue; + + int dist = Utility.Distance(item.GetWorldPosition(), Position); + if (item != DragDropManager.Holding && (dist > MultiVisRange || (!item.IsMulti && dist > VisRange))) + item.Remove(); + } + + ilist = null; + + base.OnPositionChanging(oldPos); + } + + public override void OnDirectionChanging(Direction oldDir) + { + AutoOpenDoors(); + } + + public override void OnMapChange(byte old, byte cur) + { + List list = new List(World.Mobiles.Values); + for (int i = 0; i < list.Count; i++) + { + Mobile m = list[i]; + if (m != this && m.Map != cur) + m.Remove(); + } + + list = null; + + World.Items.Clear(); + for (int i = 0; i < Contains.Count; i++) + { + Item item = (Item)Contains[i]; + World.AddItem(item); + item.Contains.Clear(); + } + + if (Config.GetBool("AutoSearch") && Backpack != null) + PlayerData.DoubleClick(Backpack); + } + + /*public override void OnMapChange( byte old, byte cur ) + { + World.Mobiles.Clear(); + World.Items.Clear(); + Counter.Reset(); + + Contains.Clear(); + + World.AddMobile( this ); + + UOAssist.PostMapChange( cur ); + }*/ + + protected override void OnNotoChange(byte old, byte cur) + { + + } + + + + + + internal void SendMessage(MsgLevel lvl, LocString loc, params object[] args) + { + SendMessage(lvl, Language.Format(loc, args)); + } + + internal void SendMessage(MsgLevel lvl, LocString loc) + { + SendMessage(lvl, Language.GetString(loc)); + } + + internal void SendMessage(LocString loc, params object[] args) + { + SendMessage(MsgLevel.Info, Language.Format(loc, args)); + } + + internal void SendMessage(LocString loc) + { + SendMessage(MsgLevel.Info, Language.GetString(loc)); + } + + /*internal void SendMessage( int hue, LocString loc, params object[] args ) + { + SendMessage( hue, Language.Format( loc, args ) ); + }*/ + + internal void SendMessage(MsgLevel lvl, string format, params object[] args) + { + SendMessage(lvl, String.Format(format, args)); + } + + internal void SendMessage(string format, params object[] args) + { + SendMessage(MsgLevel.Info, String.Format(format, args)); + } + + internal void SendMessage(string text) + { + SendMessage(MsgLevel.Info, text); + } + + internal void SendMessage(MsgLevel lvl, string text) + { + if (lvl >= (MsgLevel)Config.GetInt("MessageLevel") && text.Length > 0) + { + int hue; + switch (lvl) + { + case MsgLevel.Error: + case MsgLevel.Warning: + hue = Config.GetInt("WarningColor"); + break; + + default: + hue = Config.GetInt("SysColor"); + break; + } + + ClientCommunication.SendToClient(new UnicodeMessage(0xFFFFFFFF, -1, MessageType.Regular, hue, 3, Language.CliLocName, "System", text)); + + PacketHandlers.SysMessages.Add(text); + + if (PacketHandlers.SysMessages.Count >= 25) + PacketHandlers.SysMessages.RemoveRange(0, 10); + } + } + + public uint CurrentGumpS, CurrentGumpI; + public GumpResponseAction LastGumpResponseAction; + public bool HasGump; + public List CurrentGumpStrings = new List(); + public string CurrentGumpRawData; + public uint CurrentMenuS; + public ushort CurrentMenuI; + public bool HasMenu; + + private ushort m_SpeechHue; + public ushort SpeechHue { get { return m_SpeechHue; } set { m_SpeechHue = value; } } + + public sbyte LocalLightLevel { get { return m_LocalLight; } set { m_LocalLight = value; } } + public byte GlobalLightLevel { get { return m_GlobalLight; } set { m_GlobalLight = value; } } + + public enum SeasonFlag + { + Spring, + Summer, + Fall, + Winter, + Desolation + } + + + public ushort Features { get { return m_Features; } set { m_Features = value; } } + public int[] MapPatches { get { return m_MapPatches; } set { m_MapPatches = value; } } + + private int m_LastSkill = -1; + public int LastSkill { get { return m_LastSkill; } set { m_LastSkill = value; } } + + private Serial m_LastObj = Serial.Zero; + public Serial LastObject { get { return m_LastObj; } set { m_LastObj = value; } } + + public IUOEntity LastObjectAsEntity => World.FindEntity( LastObject ); + + + private int m_LastSpell = -1; + public int LastSpell { get { return m_LastSpell; } set { m_LastSpell = value; } } + + public byte WalkSequence { get; internal set; } + public Item LastContainer { get; internal set; } + public DateTime LastContainerOpenedAt { get; internal set; } = DateTime.UtcNow; + public DateTime LastGumpOpenedAt { get; internal set; } = DateTime.UtcNow; + public uint LastGumpX { get; internal set; } + public uint LastGumpY { get; internal set; } + public int LastGumpWidth { get; internal set; } + public int LastGumpHeight { get; internal set; } + public string LastSystemMessage { get; internal set; } + public uint LastContainerGumpGraphic { get; internal set; } + + //private UOEntity m_LastCtxM = null; + //public UOEntity LastContextMenu { get { return m_LastCtxM; } set { m_LastCtxM = value; } } + + public static bool DoubleClick(object clicked) + { + return DoubleClick(clicked, true); + } + + public static bool DoubleClick(object clicked, bool silent) + { + Serial s; + if (clicked is Mobile) + s = ((Mobile)clicked).Serial.Value; + else if (clicked is Item) + s = ((Item)clicked).Serial.Value; + else if (clicked is Serial) + s = ((Serial)clicked).Value; + else + s = Serial.Zero; + + if (s != Serial.Zero) + { + Item free = null, pack = World.Player.Backpack; + if (s.IsItem && pack != null && Config.GetBool("PotionEquip")) + { + Item i = World.FindItem(s); + if (i != null && i.IsPotion && i.ItemID != 3853) // dont unequip for exploison potions + { + // dont worry about uneqipping RuneBooks or SpellBooks + Item left = World.Player.GetItemOnLayer(Layer.LeftHand); + Item right = World.Player.GetItemOnLayer(Layer.RightHand); + + if (left != null && (right != null || left.IsTwoHanded)) + free = left; + else if (right != null && right.IsTwoHanded) + free = right; + + if (free != null) + { + if (DragDropManager.HasDragFor(free.Serial)) + free = null; + else + DragDropManager.DragDrop(free, pack); + } + } + } + + ActionQueue.DoubleClick(silent, s); + + if (free != null) + DragDropManager.DragDrop(free, World.Player, free.Layer, true); + + if (s.IsItem) + World.Player.m_LastObj = s; + } + + return false; + } + } +} diff --git a/Core/Serial.cs b/Core/Serial.cs new file mode 100644 index 0000000..a9e5bdb --- /dev/null +++ b/Core/Serial.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; + +namespace Assistant +{ + public struct Serial : IComparable + { + private uint m_Serial; + public static List Serials { get; set; } + + public static readonly Serial MinusOne = new Serial( 0xFFFFFFFF ); + public static readonly Serial Zero = new Serial( 0 ); + + private Serial( uint serial ) + { + m_Serial = serial; + } + + public uint Value + { + get + { + return m_Serial; + } + } + + public bool IsMobile + { + get + { + return ( m_Serial > 0 && m_Serial < 0x40000000 ); + } + } + + public bool IsItem + { + get + { + return ( m_Serial >= 0x40000000 && m_Serial <= 0x7FFFFF00 ); + } + } + + public bool IsValid + { + get + { + return ( m_Serial > 0 && m_Serial <= 0x7FFFFF00 ); + } + } + + public override int GetHashCode() + { + return (int)m_Serial; + } + + public int CompareTo( object o ) + { + if ( o == null ) return 1; + else if ( !(o is Serial) ) throw new ArgumentException(); + + uint ser = ((Serial)o).m_Serial; + + if ( m_Serial > ser ) return 1; + else if ( m_Serial < ser ) return -1; + else return 0; + } + + public override bool Equals( object o ) + { + if ( o == null || !(o is Serial) ) return false; + + return ((Serial)o).m_Serial == m_Serial; + } + + public static bool operator == ( Serial l, Serial r ) + { + return l.m_Serial == r.m_Serial; + } + + public static bool operator != ( Serial l, Serial r ) + { + return l.m_Serial != r.m_Serial; + } + + public static bool operator > ( Serial l, Serial r ) + { + return l.m_Serial > r.m_Serial; + } + + public static bool operator < ( Serial l, Serial r ) + { + return l.m_Serial < r.m_Serial; + } + + public static bool operator >= ( Serial l, Serial r ) + { + return l.m_Serial >= r.m_Serial; + } + + public static bool operator <= ( Serial l, Serial r ) + { + return l.m_Serial <= r.m_Serial; + } + + public override string ToString() + { + return String.Format( "0x{0:X}", m_Serial ); + } + + public static Serial Parse( string s ) + { + if ( s.StartsWith( "0x" ) ) + return (Serial)Convert.ToUInt32( s.Substring( 2 ), 16 ); + else + return (Serial)Convert.ToUInt32( s ); + } + + public static implicit operator uint( Serial a ) + { + return a.m_Serial; + } + + public static implicit operator int( Serial a ) + { + return (int)a.m_Serial; + } + + public static implicit operator Serial( uint a ) + { + return new Serial( a ); + } + } +} + diff --git a/Core/SkillTimer.cs b/Core/SkillTimer.cs new file mode 100644 index 0000000..2b12ed3 --- /dev/null +++ b/Core/SkillTimer.cs @@ -0,0 +1,59 @@ +using System; + +namespace Assistant +{ + public class SkillTimer + { + private static int m_Count; + private static Timer m_Timer; + + static SkillTimer() + { + m_Timer = new InternalTimer(); + } + + public static int Count + { + get { return m_Count; } + } + + public static bool Running + { + get { return m_Timer.Running; } + } + + public static void Start() + { + m_Count = 0; + + if (m_Timer.Running) + { + m_Timer.Stop(); + } + + m_Timer.Start(); + } + + public static void Stop() + { + m_Timer.Stop(); + } + + private class InternalTimer : Timer + { + public InternalTimer() : base(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1)) + { + } + + protected override void OnTick() + { + m_Count++; + if (m_Count > 10) + { + Stop(); + } + + } + } + } +} \ No newline at end of file diff --git a/Core/Spells.cs b/Core/Spells.cs new file mode 100644 index 0000000..3f7a508 --- /dev/null +++ b/Core/Spells.cs @@ -0,0 +1,393 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace Assistant +{ + public class Spell + { + public enum SpellFlag + { + None = '?', + Beneficial = 'B', + Harmful = 'H', + Neutral = 'N' + } + + readonly public SpellFlag Flag; + readonly public int Circle; + readonly public int Number; + readonly public string WordsOfPower; + readonly public string[] Reagents; + + private static Timer m_UnflagTimer; + + public Spell(char flag, int n, int c, string power, string[] reags) + { + Flag = (SpellFlag) flag; + Number = n; + Circle = c; + WordsOfPower = power; + Reagents = reags; + } + + public int Name + { + get + { + if (Circle <= 8) // Mage + return 3002011 + ((Circle - 1) * 8) + (Number - 1); + else if (Circle == 10) // Necr + return 1060509 + Number - 1; + else if (Circle == 20) // Chiv + return 1060585 + Number - 1; + else if (Circle == 40) // Bush + return 1060595 + Number - 1; + else if (Circle == 50) // Ninj + return 1060610 + Number - 1; + else if (Circle == 60) // Elfs + return 1071026 + Number - 1; + else + return -1; + } + } + + public override string ToString() + { + return String.Format("{0} (#{1})", Language.GetString(this.Name), GetID()); + } + + public int GetID() + { + return ToID(Circle, Number); + } + + public int GetHue(int def) + { + if (Config.GetBool("ForceSpellHue")) + { + switch (Flag) + { + case SpellFlag.Beneficial: + return Config.GetInt("BeneficialSpellHue"); + case SpellFlag.Harmful: + return Config.GetInt("HarmfulSpellHue"); + case SpellFlag.Neutral: + return Config.GetInt("NeutralSpellHue"); + default: + return def; + } + } + else + { + return def; + } + } + + public void OnCast(Packet p) + { + Cast(); + ClientCommunication.SendToServer(p); + } + + public void OnCast(int idx) + { + Cast(); + ClientCommunication.CastSpell(idx); + } + + private void Cast() + { + if (Config.GetBool("SpellUnequip")) + { + Item pack = World.Player.Backpack; + if (pack != null) + { + // dont worry about uneqipping RuneBooks or SpellBooks + Item item = World.Player.GetItemOnLayer(Layer.RightHand); +#if DEBUG + if (item != null && item.ItemID != 0x22C5 && item.ItemID != 0xE3B && item.ItemID != 0xEFA && + !item.IsVirtueShield) +#else + if ( item != null && item.ItemID != 0x22C5 && item.ItemID != 0xE3B && item.ItemID != 0xEFA ) +#endif + { + DragDropManager.Drag(item, item.Amount); + DragDropManager.Drop(item, pack); + } + + item = World.Player.GetItemOnLayer(Layer.LeftHand); +#if DEBUG + if (item != null && item.ItemID != 0x22C5 && item.ItemID != 0xE3B && item.ItemID != 0xEFA && + !item.IsVirtueShield) +#else + if ( item != null && item.ItemID != 0x22C5 && item.ItemID != 0xE3B && item.ItemID != 0xEFA ) +#endif + { + DragDropManager.Drag(item, item.Amount); + DragDropManager.Drop(item, pack); + } + } + } + + + + if (World.Player != null) + { + World.Player.LastSpell = GetID(); + LastCastTime = DateTime.UtcNow; + Targeting.SpellTargetID = 0; + } + } + + public static DateTime LastCastTime = DateTime.MinValue; + + + + private static Dictionary m_SpellsByPower; + private static Dictionary m_SpellsByID; + + static Spell() + { + + + + } + + public static void HealOrCureSelf() + { + Spell s = null; + + + { + if (World.Player.Poisoned) + { + s = Get(2, 3); // cure + } + else if (World.Player.Hits + 2 < World.Player.HitsMax) + { + if (World.Player.Hits + 30 < World.Player.HitsMax && World.Player.Mana >= 12) + s = Get(4, 5); // greater heal + else + s = Get(1, 4); // mini heal + } + else + { + if (World.Player.Mana >= 12) + s = Get(4, 5); // greater heal + else + s = Get(1, 4); // mini heal + } + } + + if (s != null) + { + if (World.Player.Poisoned || World.Player.Hits < World.Player.HitsMax) + Targeting.TargetSelf(true); + ClientCommunication.SendToServer(new CastSpellFromMacro((ushort) s.GetID())); + s.Cast(); + } + } + + public static void MiniHealOrCureSelf() + { + Spell s = null; + + + { + if (World.Player.Poisoned) + s = Get(2, 3); // cure + else + s = Get(1, 4); // mini heal + } + + if (s != null) + { + if (World.Player.Poisoned || World.Player.Hits < World.Player.HitsMax) + Targeting.TargetSelf(true); + ClientCommunication.SendToServer(new CastSpellFromMacro((ushort) s.GetID())); + s.Cast(); + } + } + + public static void Interrupt() + { + Item item = FindUsedLayer(); + + if (item != null) + { + ClientCommunication.SendToServer(new LiftRequest(item, 1)); // unequip + ClientCommunication.SendToServer(new EquipRequest(item.Serial, World.Player, item.Layer)); // Equip + } + } + + internal static Item FindUsedLayer() + { + Item layeredItem = World.Player.GetItemOnLayer(Layer.Shoes); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Pants); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Shirt); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Head); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Gloves); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Ring); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Neck); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Waist); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.InnerTorso); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Bracelet); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.MiddleTorso); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Earrings); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Arms); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.Cloak); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.OuterTorso); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.OuterLegs); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.InnerLegs); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.RightHand); + if (layeredItem != null) + return layeredItem; + + layeredItem = World.Player.GetItemOnLayer(Layer.LeftHand); + if (layeredItem != null) + return layeredItem; + + return null; + } + + public static void Initialize() + { + string filename = Path.Combine( Engine.RootPath, "spells.def" ); + m_SpellsByPower = new Dictionary( 64 + 10 + 16 ); + m_SpellsByID = new Dictionary( 64 + 10 + 16 ); + + if ( !File.Exists( filename ) ) + { + MessageBox.Show( "CEUO", "No spells.def", + MessageBoxButtons.OK, MessageBoxIcon.Warning ); + return; + } + + using ( StreamReader reader = new StreamReader( filename ) ) + { + string line; + while ( ( line = reader.ReadLine() ) != null ) + { + line = line.Trim(); + if ( line.Length <= 0 || line[0] == '#' ) + continue; + string[] split = line.Split( '|' ); + + try + { + if ( split.Length >= 5 ) + { + string[] reags = new string[split.Length - 5]; + for ( int i = 5; i < split.Length; i++ ) + reags[i - 5] = split[i].ToLower().Trim(); + Spell s = new Spell( split[0].Trim()[0], Convert.ToInt32( split[1].Trim() ), + Convert.ToInt32( split[2].Trim() ), /*split[3].Trim(),*/ split[4].Trim(), reags ); + + m_SpellsByID[s.GetID()] = s; + + if ( s.WordsOfPower != null && s.WordsOfPower.Trim().Length > 0 ) + m_SpellsByPower[s.WordsOfPower] = s; + } + } + catch + { + } + } + } + } + + public static void OnHotKey(ref object state) + { + ushort id = (ushort) state; + Spell s = Spell.Get(id); + if (s != null) + { + s.OnCast(new CastSpellFromMacro(id)); + //if ( Macros.MacroManager.AcceptActions ) + // Macros.MacroManager.Action( new Macros.MacroCastSpellAction( s ) ); + } + } + + public static int ToID(int circle, int num) + { + if (circle < 10) + return ((circle - 1) * 8) + num; + else + return (circle * 10) + num; + } + + public static Spell Get(string power) + { + Spell s; + m_SpellsByPower.TryGetValue(power, out s); + return s; + } + + public static Spell Get(int num) + { + Spell s; + m_SpellsByID.TryGetValue(num, out s); + return s; + } + + public static Spell Get(int circle, int num) + { + return Get(Spell.ToID(circle, num)); + } + } +} \ No newline at end of file diff --git a/Core/Targeting.cs b/Core/Targeting.cs new file mode 100644 index 0000000..8a6d14d --- /dev/null +++ b/Core/Targeting.cs @@ -0,0 +1,1705 @@ +using CEasyUO; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public class TargetInfo + { + public byte Type; + public uint TargID; + public byte Flags; + public Serial Serial; + public int X, Y; + public int Z; + public ushort Gfx; + } + + public class Targeting + { + public const uint LocalTargID = 0x7FFFFFFF; // uid for target sent from razor + + public delegate void TargetResponseCallback(bool location, Serial serial, Point3D p, ushort gfxid); + public delegate void CancelTargetCallback(); + + private static CancelTargetCallback m_OnCancel; + private static TargetResponseCallback m_OnTarget; + + private static bool m_Intercept; + private static bool m_HasTarget; + private static bool m_ClientTarget; + private static TargetInfo m_LastTarget; + public static TargetInfo LastTarget => m_LastTarget != null ? m_LastTarget : m_LastGroundTarg; + private static TargetInfo m_LastGroundTarg; + private static TargetInfo m_LastBeneTarg; + private static TargetInfo m_LastHarmTarg; + + private static bool m_AllowGround; + private static uint m_CurrentID; + private static byte m_CurFlags; + + private static uint m_PreviousID; + private static bool m_PreviousGround; + private static byte m_PrevFlags; + + private static Serial m_LastCombatant; + + private delegate bool QueueTarget(); + private static QueueTarget TargetSelfAction = new QueueTarget(DoTargetSelf); + private static QueueTarget LastTargetAction = new QueueTarget(DoLastTarget); + private static QueueTarget m_QueueTarget; + + + private static uint m_SpellTargID = 0; + public static uint SpellTargetID { get { return m_SpellTargID; } set { m_SpellTargID = value; } } + + private static List m_FilterCancel = new List(); + + public static bool HasTarget { get { return m_HasTarget; } } + + + private static List m_MonsterIds = new List() + { + 0x1, 0x2, 0x3, 0x4, 0x7, 0x8, 0x9, 0xC, 0xD, 0xE, 0xF, + 0x10, 0x11, 0x12, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, + 0x1E, 0x1F, 0x21, 0x23, 0x24, 0x25, 0x27, 0x29, 0x2A, 0x2C, + 0x2D, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3B, 0x3C, 0x3D, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4B, 0x4F, 0x50, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5A, + 0x5B, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x62, 0x69, 0x6A, 0x6B, 0x6C, + 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x87, 0x88, 0x89, + 0x8A, 0x8B, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x96, 0x99, 0x9B, 0x9E, + 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xB4, 0x4C, 0x4D, 0x3D + }; + + public static void Initialize() + { + PacketHandler.RegisterClientToServerViewer(0x6C, new PacketViewerCallback(TargetResponse)); + PacketHandler.RegisterServerToClientViewer(0x6C, new PacketViewerCallback(NewTarget)); + PacketHandler.RegisterServerToClientViewer(0xAA, new PacketViewerCallback(CombatantChange)); + + + } + + private static void CombatantChange(PacketReader p, PacketHandlerEventArgs e) + { + Serial ser = p.ReadUInt32(); + if (ser.IsMobile && ser != World.Player.Serial && ser != Serial.Zero && ser != Serial.MinusOne) + m_LastCombatant = ser; + } + + private static void AttackLastComb() + { + if (m_LastCombatant.IsMobile) + ClientCommunication.SendToServer(new AttackReq(m_LastCombatant)); + } + + private static void AttackLastTarg() + { + if (m_LastTarget != null && m_LastTarget.Serial.IsMobile) + ClientCommunication.SendToServer(new AttackReq(m_LastTarget.Serial)); + } + + private static void OnClearQueue() + { + ClearQueue(); + + + } + + internal static void OneTimeTarget(TargetResponseCallback onTarget) + { + OneTimeTarget(false, onTarget, null); + } + + internal static void OneTimeTarget(bool ground, TargetResponseCallback onTarget) + { + OneTimeTarget(ground, onTarget, null); + } + + internal static void OneTimeTarget(TargetResponseCallback onTarget, CancelTargetCallback onCancel) + { + OneTimeTarget(false, onTarget, onCancel); + } + + internal static void OneTimeTarget(bool ground, TargetResponseCallback onTarget, CancelTargetCallback onCancel) + { + if (m_Intercept && m_OnCancel != null) + { + m_OnCancel(); + CancelOneTimeTarget(); + } + + if (m_HasTarget && m_CurrentID != 0 && m_CurrentID != LocalTargID) + { + m_PreviousID = m_CurrentID; + m_PreviousGround = m_AllowGround; + m_PrevFlags = m_CurFlags; + + m_FilterCancel.Add(m_PreviousID); + } + + m_Intercept = true; + m_CurrentID = LocalTargID; + m_OnTarget = onTarget; + m_OnCancel = onCancel; + + m_ClientTarget = m_HasTarget = true; + ClientCommunication.SendToClient(new Target(LocalTargID, ground)); + ClearQueue(); + } + + internal static void CancelOneTimeTarget() + { + m_ClientTarget = m_HasTarget = false; + + ClientCommunication.SendToClient(new CancelTarget(LocalTargID)); + EndIntercept(); + } + + private static bool m_LTWasSet; + public static void TargetSetLastTarget() + { + if (World.Player != null) + { + m_LTWasSet = false; + OneTimeTarget(false, new TargetResponseCallback(OnSetLastTarget), new CancelTargetCallback(OnSLTCancel)); + } + } + + private static void OnSLTCancel() + { + if (m_LastTarget != null) + m_LTWasSet = true; + } + + private static void OnSetLastTarget(bool location, Serial serial, Point3D p, ushort gfxid) + { + if (serial == World.Player.Serial) + { + OnSLTCancel(); + return; + } + + m_LastBeneTarg = m_LastHarmTarg = m_LastGroundTarg = m_LastTarget = new TargetInfo(); + m_LastTarget.Flags = 0; + m_LastTarget.Gfx = gfxid; + m_LastTarget.Serial = serial; + m_LastTarget.Type = (byte)(location ? 1 : 0); + m_LastTarget.X = p.X; + m_LastTarget.Y = p.Y; + m_LastTarget.Z = p.Z; + + m_LTWasSet = true; + + if (serial.IsMobile) + { + LastTargetChanged(); + ClientCommunication.SendToClient(new ChangeCombatant(serial)); + m_LastCombatant = serial; + } + } + + private static Serial m_OldLT = Serial.Zero; + + private static void RemoveTextFlags(UOEntity m) + { + if (m != null) + { + bool oplchanged = false; + + if (oplchanged) + m.OPLChanged(); + } + } + + private static void AddTextFlags(UOEntity m) + { + + } + + private static void LastTargetChanged() + { + if (m_LastTarget != null) + { + bool lth = Config.GetInt("LTHilight") != 0; + + if (m_OldLT.IsItem) + { + RemoveTextFlags(World.FindItem(m_OldLT)); + } + else + { + Mobile m = World.FindMobile(m_OldLT); + if (m != null) + { + if (lth) + ClientCommunication.SendToClient(new MobileIncoming(m)); + + RemoveTextFlags(m); + } + } + + if (m_LastTarget.Serial.IsItem) + { + AddTextFlags(World.FindItem(m_LastTarget.Serial)); + } + else + { + Mobile m = World.FindMobile(m_LastTarget.Serial); + if (m != null) + { + if (IsLastTarget(m) && lth) + ClientCommunication.SendToClient(new MobileIncoming(m)); + + CheckLastTargetRange(m); + + AddTextFlags(m); + } + } + + m_OldLT = m_LastTarget.Serial; + } + } + + public static bool LTWasSet { get { return m_LTWasSet; } } + + public static void TargetRandNonFriendly() + { + RandomTarget(3, 4, 5, 6); + } + + public static void TargetRandFriendly() + { + RandomTarget(0, 1, 2); + } + + public static void TargetRandEnemy() + { + RandomTarget(5); + } + + public static void TargetRandEnemyMonster() + { + RandomMonsterTarget(5); + } + + public static void TargetRandEnemyHumanoid() + { + RandomHumanoidTarget(5); + } + + public static void TargetRandRed() + { + RandomTarget(6); + } + + public static void TargetRandGrey() + { + RandomTarget(3, 4); + } + + public static void TargetRandGreyMonster() + { + RandomMonsterTarget(3, 4); + } + + public static void TargetRandGreyHumanoid() + { + RandomHumanoidTarget(3, 4); + } + + public static void TargetRandCriminal() + { + RandomTarget(4); + } + + public static void TargetRandCriminalHumanoid() + { + RandomHumanoidTarget(4); + } + + public static void TargetRandInnocent() + { + RandomTarget(1); + } + + public static void TargetRandInnocentHumanoid() + { + RandomHumanoidTarget(1); + } + + public static void TargetRandAnyone() + { + RandomTarget(); + } + + public static void RandomTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if ((noto.Length > 0 && noto[0] == 0) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + if (list.Count > 0) + SetLastTargetTo((Mobile)list[Utility.Random(list.Count)]); + + } + + public static void RandomHumanoidTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if (m.Body != 0x0190 && m.Body != 0x0191 && m.Body != 0x025D && m.Body != 0x025E) + continue; + + if ((noto.Length > 0 && noto[0] == 0) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + if (list.Count > 0) + SetLastTargetTo(list[Utility.Random(list.Count)]); + + } + + public static void RandomMonsterTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if (!m.IsMonster) + continue; + + if ((noto.Length > 0 && noto[0] == 0) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + if (list.Count > 0) + SetLastTargetTo(list[Utility.Random(list.Count)]); + + } + + + public static void TargetCloseNonFriendly() + { + ClosestTarget(3, 4, 5, 6); + } + + public static void TargetCloseFriendly() + { + ClosestTarget(0, 1, 2); + } + + public static void TargetCloseEnemy() + { + ClosestTarget(5); + } + + public static void TargetCloseEnemyHumanoid() + { + ClosestHumanoidTarget(5); + } + + public static void TargetCloseEnemyMonster() + { + ClosestMonsterTarget(5); + } + + public static void TargetCloseRed() + { + ClosestTarget(6); + } + + public static void TargetCloseGrey() + { + ClosestTarget(3, 4); + } + + public static void TargetCloseGreyMonster() + { + ClosestMonsterTarget(3, 4); + } + + public static void TargetCloseGreyHumanoid() + { + ClosestHumanoidTarget(3, 4); + } + + public static void TargetCloseCriminal() + { + ClosestTarget(4); + } + + public static void TargetCloseCriminalHumanoid() + { + ClosestHumanoidTarget(4); + } + + public static void TargetCloseInnocent() + { + ClosestTarget(1); + } + + public static void TargetCloseInnocentHumanoid() + { + ClosestHumanoidTarget(1); + } + + public static void TargetClosest() + { + ClosestTarget(); + } + + public static void ClosestTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if ((noto.Length > 0 && noto[0] == 0) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + Mobile closest = null; + double closestDist = double.MaxValue; + + foreach (Mobile m in list) + { + double dist = Utility.DistanceSqrt(m.Position, World.Player.Position); + + if (dist < closestDist || closest == null) + { + closestDist = dist; + closest = m; + } + } + + if (closest != null) + SetLastTargetTo(closest); +; + } + + public static void ClosestHumanoidTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if (m.Body != 0x0190 && m.Body != 0x0191 && m.Body != 0x025D && m.Body != 0x025E) + continue; + + if (((noto.Length > 0 && noto[0] == 0)) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + Mobile closest = null; + double closestDist = double.MaxValue; + + foreach (Mobile m in list) + { + double dist = Utility.DistanceSqrt(m.Position, World.Player.Position); + + if (dist < closestDist || closest == null) + { + closestDist = dist; + closest = m; + } + } + + if (closest != null) + SetLastTargetTo(closest); + + } + + public static void ClosestMonsterTarget(params int[] noto) + { + + + List list = new List(); + foreach (Mobile m in World.MobilesInRange(12)) + { + if (!m.IsMonster) + continue; + + if (((noto.Length > 0 && noto[0] == 0)) && + !m.Blessed && !m.IsGhost && m.Serial != World.Player.Serial && + Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + for (int i = 0; i < noto.Length; i++) + { + if (noto[i] == m.Notoriety) + { + list.Add(m); + break; + } + } + + if (noto.Length == 0) + list.Add(m); + } + } + + Mobile closest = null; + double closestDist = double.MaxValue; + + foreach (Mobile m in list) + { + double dist = Utility.DistanceSqrt(m.Position, World.Player.Position); + + if (dist < closestDist || closest == null) + { + closestDist = dist; + closest = m; + } + } + + if (closest != null) + SetLastTargetTo(closest); + + } + public static void SetLastTargetTo( uint serial ) + { + TargetInfo targ = new TargetInfo(); + m_LastGroundTarg = m_LastTarget = targ; + + var m = World.FindEntity( serial ); + + targ.Type = 0; + if ( m_HasTarget ) + targ.Flags = m_CurFlags; + + + targ.Gfx = m.GraphicID; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + } + public static void SetLastTargetTo(Mobile m) + { + SetLastTargetTo(m, 0); + } + + public static void SetLastTargetTo(Mobile m, byte flagType) + { + TargetInfo targ = new TargetInfo(); + m_LastGroundTarg = m_LastTarget = targ; + + if ((m_HasTarget && m_CurFlags == 1) || flagType == 1) + m_LastHarmTarg = targ; + else if ((m_HasTarget && m_CurFlags == 2) || flagType == 2) + m_LastBeneTarg = targ; + else if (flagType == 0) + m_LastHarmTarg = m_LastBeneTarg = targ; + + targ.Type = 0; + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Flags = flagType; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + + + TargetLast(); + LastTargetChanged(); + } + + private static void EndIntercept() + { + m_Intercept = false; + m_OnTarget = null; + m_OnCancel = null; + } + + public static void TargetSelf() + { + TargetSelf(false); + } + + public static void TargetSelf(bool forceQ) + { + if (World.Player == null) + return; + + //if ( Macros.MacroManager.AcceptActions ) + // MacroManager.Action( new TargetSelfAction() ); + + if (m_HasTarget) + { + if (!DoTargetSelf()) + ResendTarget(); + } + else if (forceQ || Config.GetBool("QueueTargets")) + { + + m_QueueTarget = TargetSelfAction; + } + } + + public static bool DoTargetSelf() + { + if (World.Player == null) + return false; + + if (CheckHealPoisonTarg(m_CurrentID, World.Player.Serial)) + return false; + + CancelClientTarget(); + m_HasTarget = false; + + if (m_Intercept) + { + TargetInfo targ = new TargetInfo(); + targ.Serial = World.Player.Serial; + targ.Gfx = World.Player.Body; + targ.Type = 0; + targ.X = World.Player.Position.X; + targ.Y = World.Player.Position.Y; + targ.Z = World.Player.Position.Z; + targ.TargID = LocalTargID; + targ.Flags = 0; + + OneTimeResponse(targ); + } + else + { + ClientCommunication.SendToServer(new TargetResponse(m_CurrentID, World.Player)); + } + + return true; + } + + public static void TargetLast() + { + TargetLast( false); + } + + public static void TargetLast( bool forceQ) + { + //if ( Macros.MacroManager.AcceptActions ) + // MacroManager.Action( new LastTargetAction() ); + + if (m_HasTarget) + { + if (!DoLastTarget()) + ResendTarget(); + } + else if (forceQ || Config.GetBool("QueueTargets")) + { + + + m_QueueTarget = LastTargetAction; + } + } + + public static bool DoLastTarget() + { + TargetInfo targ; + if (Config.GetBool("SmartLastTarget")) + { + if (m_AllowGround && m_LastGroundTarg != null) + targ = m_LastGroundTarg; + else if (m_CurFlags == 1) + targ = m_LastHarmTarg; + else if (m_CurFlags == 2) + targ = m_LastBeneTarg; + else + targ = m_LastTarget; + + if (targ == null) + targ = m_LastTarget; + } + else + { + if (m_AllowGround && m_LastGroundTarg != null) + targ = m_LastGroundTarg; + else + targ = m_LastTarget; + } + + if (targ == null) + return false; + + Point3D pos = Point3D.Zero; + if (targ.Serial.IsMobile) + { + Mobile m = World.FindMobile(targ.Serial); + if (m != null) + { + pos = m.Position; + + targ.X = pos.X; + targ.Y = pos.Y; + targ.Z = pos.Z; + } + else + { + pos = Point3D.Zero; + } + } + else if (targ.Serial.IsItem) + { + Item i = World.FindItem(targ.Serial); + if (i != null) + { + pos = i.GetWorldPosition(); + + targ.X = i.Position.X; + targ.Y = i.Position.Y; + targ.Z = i.Position.Z; + } + else + { + pos = Point3D.Zero; + targ.X = targ.Y = targ.Z = 0; + } + } + else + { + if (!m_AllowGround && (targ.Serial == Serial.Zero || targ.Serial >= 0x80000000)) + { + return false; + } + else + { + pos = new Point3D(targ.X, targ.Y, targ.Z); + } + } + + if (Config.GetBool("RangeCheckLT") && (pos == Point3D.Zero || !Utility.InRange(World.Player.Position, pos, Config.GetInt("LTRange")))) + { + if (Config.GetBool("QueueTargets")) + m_QueueTarget = LastTargetAction; + return false; + } + + if (CheckHealPoisonTarg(m_CurrentID, targ.Serial)) + return false; + + CancelClientTarget(); + m_HasTarget = false; + + targ.TargID = m_CurrentID; + + if (m_Intercept) + OneTimeResponse(targ); + else + ClientCommunication.SendToServer(new TargetResponse(targ)); + return true; + } + + public static void ClearQueue() + { + m_QueueTarget = null; + } + + private static TimerCallbackState m_OneTimeRespCallback = new TimerCallbackState(OneTimeResponse); + + private static void OneTimeResponse(object state) + { + TargetInfo info = state as TargetInfo; + + if (info != null) + { + if ((info.X == 0xFFFF && info.X == 0xFFFF) && (info.Serial == 0 || info.Serial >= 0x80000000)) + { + if (m_OnCancel != null) + m_OnCancel(); + } + else + { + + if (m_OnTarget != null) + m_OnTarget(info.Type == 1 ? true : false, info.Serial, new Point3D(info.X, info.Y, info.Z), info.Gfx); + } + } + + EndIntercept(); + } + + private static void CancelTarget() + { + OnClearQueue(); + CancelClientTarget(); + + if (m_HasTarget) + { + ClientCommunication.SendToServer(new TargetCancelResponse(m_CurrentID)); + m_HasTarget = false; + } + } + + private static void CancelClientTarget() + { + if (m_ClientTarget) + { + m_FilterCancel.Add((uint)m_CurrentID); + ClientCommunication.SendToClient(new CancelTarget(m_CurrentID)); + m_ClientTarget = false; + } + } + + public static void Target(TargetInfo info) + { + if (m_Intercept) + { + OneTimeResponse(info); + } + else if (m_HasTarget) + { + info.TargID = m_CurrentID; + m_LastGroundTarg = m_LastTarget = info; + ClientCommunication.SendToServer(new TargetResponse(info)); + } + + CancelClientTarget(); + m_HasTarget = false; + } + + public static void Target(Point3D pt) + { + TargetInfo info = new TargetInfo(); + info.Type = 1; + info.Flags = 0; + info.Serial = 0; + info.X = pt.X; + info.Y = pt.Y; + info.Z = pt.Z; + info.Gfx = 0; + + Target(info); + } + + public static void Target(Point3D pt, int gfx) + { + TargetInfo info = new TargetInfo(); + info.Type = 1; + info.Flags = 0; + info.Serial = 0; + info.X = pt.X; + info.Y = pt.Y; + info.Z = pt.Z; + info.Gfx = (ushort)(gfx & 0x3FFF); + + Target(info); + } + + public static void Target(Serial s) + { + TargetInfo info = new TargetInfo(); + info.Type = 0; + info.Flags = 0; + info.Serial = s; + + if (s.IsItem) + { + Item item = World.FindItem(s); + if (item != null) + { + info.X = item.Position.X; + info.Y = item.Position.Y; + info.Z = item.Position.Z; + info.Gfx = item.ItemID; + } + } + else if (s.IsMobile) + { + Mobile m = World.FindMobile(s); + if (m != null) + { + info.X = m.Position.X; + info.Y = m.Position.Y; + info.Z = m.Position.Z; + info.Gfx = m.Body; + } + } + + Target(info); + } + + public static void Target(object o) + { + if (o is Item) + { + Item item = (Item)o; + TargetInfo info = new TargetInfo(); + info.Type = 0; + info.Flags = 0; + info.Serial = item.Serial; + info.X = item.Position.X; + info.Y = item.Position.Y; + info.Z = item.Position.Z; + info.Gfx = item.ItemID; + Target(info); + } + else if (o is Mobile) + { + Mobile m = (Mobile)o; + TargetInfo info = new TargetInfo(); + info.Type = 0; + info.Flags = 0; + info.Serial = m.Serial; + info.X = m.Position.X; + info.Y = m.Position.Y; + info.Z = m.Position.Z; + info.Gfx = m.Body; + Target(info); + } + else if (o is Serial) + { + Target((Serial)o); + } + else if (o is TargetInfo) + { + Target((TargetInfo)o); + } + } + + public static void CheckTextFlags(Mobile m) + { + + + } + + public static bool IsLastTarget(Mobile m) + { + if (m != null) + { + if (Config.GetBool("SmartLastTarget")) + { + if (m_LastHarmTarg != null && m_LastHarmTarg.Serial == m.Serial) + return true; + } + else + { + if (m_LastTarget != null && m_LastTarget.Serial == m.Serial) + return true; + } + } + + return false; + } + + private static int m_NextPrevTargIdx = 0; + public static void NextTarget() + { + List list = World.MobilesInRange(12); + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + if (list.Count <= 0) + { + + return; + } + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargIdx++; + + if (m_NextPrevTargIdx >= list.Count) + m_NextPrevTargIdx = 0; + + m = (Mobile)list[m_NextPrevTargIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + + /*if ( m_HasTarget ) + { + DoLastTarget(); + ClearQueue(); + }*/ + } + + private static int m_NextPrevTargHumanoidIdx = 0; + public static void NextTargetHumanoid() + { + List mobiles = World.MobilesInRange(12); + List list = new List(); + + foreach (Mobile mob in mobiles) + { + if (mob.Body == 0x0190 || mob.Body == 0x0191 || mob.Body == 0x025D || mob.Body == 0x025E) + list.Add(mob); + } + + if (list.Count <= 0) + { + return; + } + + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargHumanoidIdx++; + + if (m_NextPrevTargHumanoidIdx >= list.Count) + m_NextPrevTargHumanoidIdx = 0; + + m = (Mobile)list[m_NextPrevTargHumanoidIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + + /*if ( m_HasTarget ) + { + DoLastTarget(); + ClearQueue(); + }*/ + } + + private static int m_NextPrevTargEnemyHumanoidIdx = 0; + public static void NextTargetEnemyHumanoid() + { + List mobiles = World.MobilesInRange(12); + List list = new List(); + + foreach (Mobile mob in mobiles) + { + if (mob.Body == 0x0190 || mob.Body == 0x0191 || mob.Body == 0x025D || mob.Body == 0x025E) + { + if (mob.Notoriety == 5) // Check if they are red + { + list.Add(mob); + } + } + } + + if (list.Count <= 0) + { + return; + } + + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargEnemyHumanoidIdx++; + + if (m_NextPrevTargEnemyHumanoidIdx >= list.Count) + m_NextPrevTargEnemyHumanoidIdx = 0; + + m = list[m_NextPrevTargEnemyHumanoidIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + + /*if ( m_HasTarget ) + { + DoLastTarget(); + ClearQueue(); + }*/ + } + + public static void PrevTarget() + { + List list = World.MobilesInRange(12); + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + if (list.Count <= 0) + { + return; + } + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargIdx--; + + if (m_NextPrevTargIdx < 0) + m_NextPrevTargIdx = list.Count - 1; + + m = (Mobile)list[m_NextPrevTargIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + } + + public static void PrevTargetHumanoid() + { + List mobiles = World.MobilesInRange(12); + List list = new List(); + + foreach (Mobile mob in mobiles) + { + if (mob.Body == 0x0190 || mob.Body == 0x0191 || mob.Body == 0x025D || mob.Body == 0x025E) + list.Add(mob); + } + + if (list.Count <= 0) + { + return; + } + + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargHumanoidIdx--; + + if (m_NextPrevTargHumanoidIdx < 0) + m_NextPrevTargHumanoidIdx = list.Count - 1; + + m = (Mobile)list[m_NextPrevTargHumanoidIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + + /*if ( m_HasTarget ) + { + DoLastTarget(); + ClearQueue(); + }*/ + } + + public static void PrevTargetEnemyHumanoid() + { + List mobiles = World.MobilesInRange(12); + List list = new List(); + + foreach (Mobile mob in mobiles) + { + if (mob.Body == 0x0190 || mob.Body == 0x0191 || mob.Body == 0x025D || mob.Body == 0x025E) + { + if (mob.Notoriety == 5) // Check if they are red + { + list.Add(mob); + } + } + } + + if (list.Count <= 0) + { + return; + } + + TargetInfo targ = new TargetInfo(); + Mobile m = null, old = World.FindMobile(m_LastTarget == null ? Serial.Zero : m_LastTarget.Serial); + + for (int i = 0; i < 3; i++) + { + m_NextPrevTargEnemyHumanoidIdx--; + + if (m_NextPrevTargEnemyHumanoidIdx < 0) + m_NextPrevTargEnemyHumanoidIdx = list.Count - 1; + + m = list[m_NextPrevTargEnemyHumanoidIdx]; + + if (m != null && m != World.Player && m != old) + break; + else + m = null; + } + + if (m == null) + m = old; + + if (m == null) + { + return; + } + + m_LastGroundTarg = m_LastTarget = targ; + + m_LastHarmTarg = m_LastBeneTarg = targ; + + if (m_HasTarget) + targ.Flags = m_CurFlags; + else + targ.Type = 0; + + targ.Gfx = m.Body; + targ.Serial = m.Serial; + targ.X = m.Position.X; + targ.Y = m.Position.Y; + targ.Z = m.Position.Z; + + ClientCommunication.SendToClient(new ChangeCombatant(m)); + m_LastCombatant = m.Serial; + } + + public static void CheckLastTargetRange(Mobile m) + { + if (World.Player == null) + return; + + if (m_HasTarget && m != null && m_LastTarget != null && m.Serial == m_LastTarget.Serial && m_QueueTarget == LastTargetAction) + { + if (Config.GetBool("RangeCheckLT") ) + { + if (Utility.InRange(World.Player.Position, m.Position, Config.GetInt("LTRange"))) + { + if (m_QueueTarget()) + ClearQueue(); + } + } + } + } + + private static bool CheckHealPoisonTarg(uint targID, Serial ser) + { + if (World.Player == null) + return false; + + if (targID == m_SpellTargID && ser.IsMobile && (World.Player.LastSpell == Spell.ToID(1, 4) || World.Player.LastSpell == Spell.ToID(4, 5)) && Config.GetBool("BlockHealPoison")) + { + Mobile m = World.FindMobile(ser); + + if (m != null && m.Poisoned) + { + return true; + } + } + + return false; + } + + private static void TargetResponse(PacketReader p, PacketHandlerEventArgs args) + { + TargetInfo info = new TargetInfo(); + info.Type = p.ReadByte(); + info.TargID = p.ReadUInt32(); + info.Flags = p.ReadByte(); + info.Serial = p.ReadUInt32(); + info.X = p.ReadUInt16(); + info.Y = p.ReadUInt16(); + info.Z = p.ReadInt16(); + info.Gfx = p.ReadUInt16(); + + m_ClientTarget = false; + + //if (Config.GetBool("ShowAttackTargetOverhead")) + //{ + // Mobile m = World.FindMobile(info.Serial); + + // if (m != null) + // { + // if (FriendsAgent.IsFriend(m)) + // { + // World.Player.OverheadMessage(63, $"Target: {m.Name}"); + // } + // else + // { + // World.Player.OverheadMessage(m.GetNotorietyColorInt(), $"Target: {m.Name}"); + // } + // } + //} + + // check for cancel + if (info.X == 0xFFFF && info.X == 0xFFFF && (info.Serial <= 0 || info.Serial >= 0x80000000)) + { + m_HasTarget = false; + + if (m_Intercept) + { + args.Block = true; + Timer.DelayedCallbackState(TimeSpan.Zero, m_OneTimeRespCallback, info).Start(); + EndIntercept(); + + if (m_PreviousID != 0) + { + m_CurrentID = m_PreviousID; + m_AllowGround = m_PreviousGround; + m_CurFlags = m_PrevFlags; + + m_PreviousID = 0; + + ResendTarget(); + } + } + else if (m_FilterCancel.Contains((uint)info.TargID) || info.TargID == LocalTargID) + { + args.Block = true; + } + + m_FilterCancel.Clear(); + return; + } + + ClearQueue(); + + if (m_Intercept) + { + if (info.TargID == LocalTargID) + { + Timer.DelayedCallbackState(TimeSpan.Zero, m_OneTimeRespCallback, info).Start(); + + m_HasTarget = false; + args.Block = true; + + if (m_PreviousID != 0) + { + m_CurrentID = m_PreviousID; + m_AllowGround = m_PreviousGround; + m_CurFlags = m_PrevFlags; + + m_PreviousID = 0; + + ResendTarget(); + } + + m_FilterCancel.Clear(); + + return; + } + else + { + EndIntercept(); + } + } + + m_HasTarget = false; + + if (CheckHealPoisonTarg(m_CurrentID, info.Serial)) + { + ResendTarget(); + args.Block = true; + } + + if (info.Serial != World.Player.Serial) + { + if (info.Serial.IsValid) + { + // only let lasttarget be a non-ground target + + m_LastTarget = info; + if (info.Flags == 1) + m_LastHarmTarg = info; + else if (info.Flags == 2) + m_LastBeneTarg = info; + + LastTargetChanged(); + } + + m_LastGroundTarg = info; // ground target is the true last target + + + } + else + { + + } + + + + m_FilterCancel.Clear(); + } + + private static void NewTarget(PacketReader p, PacketHandlerEventArgs args) + { + bool prevAllowGround = m_AllowGround; + uint prevID = m_CurrentID; + byte prevFlags = m_CurFlags; + bool prevClientTarget = m_ClientTarget; + + m_AllowGround = p.ReadBoolean(); // allow ground + m_CurrentID = p.ReadUInt32(); // target uid + m_CurFlags = p.ReadByte(); // flags + // the rest of the packet is 0s + // if (m_CurrentID == LocalTargID) + // return; + // check for a server cancel command + if (!m_AllowGround && m_CurrentID == 0 && m_CurFlags == 3) + { + m_HasTarget = false; + m_ClientTarget = false; + if (m_Intercept) + { + EndIntercept(); + } + return; + } + + if (Spell.LastCastTime + TimeSpan.FromSeconds(3.0) > DateTime.UtcNow && Spell.LastCastTime + TimeSpan.FromSeconds(0.5) <= DateTime.UtcNow && m_SpellTargID == 0) + m_SpellTargID = m_CurrentID; + + m_HasTarget = true; + m_ClientTarget = false; + +if (m_QueueTarget != null && m_QueueTarget()) + { + ClearQueue(); + args.Block = true; + } + + if (args.Block) + { + if (prevClientTarget) + { + m_AllowGround = prevAllowGround; + m_CurrentID = prevID; + m_CurFlags = prevFlags; + + m_ClientTarget = true; + + if (!m_Intercept) + CancelClientTarget(); + } + } + else + { + m_ClientTarget = true; + + if (m_Intercept) + { + if (m_OnCancel != null) + m_OnCancel(); + EndIntercept(); + + m_FilterCancel.Add((uint)prevID); + } + } + } + + public static void ResendTarget() + { + if (!m_ClientTarget || !m_HasTarget) + { + CancelClientTarget(); + m_ClientTarget = m_HasTarget = true; + ClientCommunication.SendToClient(new Target(m_CurrentID, m_AllowGround, m_CurFlags)); + } + } + } +} diff --git a/Core/Timer.cs b/Core/Timer.cs new file mode 100644 index 0000000..a27a928 --- /dev/null +++ b/Core/Timer.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; + +namespace Assistant +{ + public class MinHeap + { + private List m_List; + private int m_Size; + + public MinHeap() + : this(1) + { + } + + public MinHeap(int capacity) + { + m_List = new List(capacity + 1); + m_Size = 0; + m_List.Add(null); // 0th index is never used, always null + } + + public MinHeap(ICollection c) + : this(c.Count) + { + foreach (IComparable o in c) + m_List.Add(o); + m_Size = c.Count; + Heapify(); + } + + public void Heapify() + { + for (int i = m_Size / 2; i > 0; i--) + PercolateDown(i); + } + + private void PercolateDown(int hole) + { + IComparable tmp = m_List[hole]; + int child; + + for (; hole * 2 <= m_Size; hole = child) + { + child = hole * 2; + if (child != m_Size && (m_List[child + 1]).CompareTo(m_List[child]) < 0) + child++; + + if (tmp.CompareTo(m_List[child]) >= 0) + m_List[hole] = m_List[child]; + else + break; + } + + m_List[hole] = tmp; + } + + public IComparable Peek() + { + return m_List[1] as IComparable; + } + + public IComparable Pop() + { + IComparable top = Peek(); + + m_List[1] = m_List[m_Size--]; + PercolateDown(1); + + return top; + } + + public void Remove(IComparable o) + { + for (int i = 1; i <= m_Size; i++) + { + if (m_List[i] == o) + { + m_List[i] = m_List[m_Size--]; + PercolateDown(i); + // TODO: Do we ever need to shrink? + return; + } + } + } + + public void Clear() + { + int capacity = m_List.Count / 2; + if (capacity < 2) + capacity = 2; + m_Size = 0; + m_List = new List(capacity) { null }; + } + + public void Add(IComparable o) + { + // PercolateUp + int hole = ++m_Size; + + // Grow the list if needed + while (m_List.Count <= m_Size) + m_List.Add(null); + + for (; hole > 1 && o.CompareTo(m_List[hole / 2]) < 0; hole /= 2) + m_List[hole] = m_List[hole / 2]; + m_List[hole] = o; + } + + public void AddMultiple(ICollection col) + { + if (col == null || col.Count <= 0) + return; + + foreach (IComparable o in col) + { + int hole = ++m_Size; + + // Grow the list as needed + while (m_List.Count <= m_Size) + m_List.Add(null); + + m_List[hole] = o; + } + + Heapify(); + } + + public int Count { get { return m_Size; } } + + public bool IsEmpty { get { return Count <= 0; } } + + public List GetRawList() + { + List copy = new List(m_Size); + for (int i = 1; i <= m_Size; i++) + copy.Add(m_List[i]); + return copy; + } + } + + public delegate void TimerCallback(); + + public delegate void TimerCallbackState(object state); + + public abstract class Timer : IComparable + { + private DateTime m_Next; + private TimeSpan m_Delay; + private TimeSpan m_Interval; + private bool m_Running; + private int m_Index, m_Count; + + protected abstract void OnTick(); + + public Timer(TimeSpan delay) + : this(delay, TimeSpan.Zero, 1) + { + } + + public Timer(TimeSpan interval, int count) + : this(interval, interval, count) + { + } + + public Timer(TimeSpan delay, TimeSpan interval) + : this(delay, interval, 0) + { + } + + public Timer(TimeSpan delay, TimeSpan interval, int count) + { + m_Delay = delay; + m_Interval = interval; + m_Count = count; + } + + public void Start() + { + if (!m_Running) + { + m_Index = 0; + m_Next = DateTime.Now + m_Delay; + m_Running = true; + m_Heap.Add(this); + ChangedNextTick(true); + } + } + + public void Stop() + { + if (!m_Running) + return; + + m_Running = false; + m_Heap.Remove(this); + //ChangedNextTick(); + } + + public int CompareTo(object obj) + { + if (obj is Timer) + return this.TimeUntilTick.CompareTo(((Timer)obj).TimeUntilTick); + else + return -1; + } + + public TimeSpan TimeUntilTick + { + get { return m_Running ? m_Next - DateTime.Now : TimeSpan.MaxValue; } + } + + public bool Running { get { return m_Running; } } + + public TimeSpan Delay + { + get { return m_Delay; } + set { m_Delay = value; } + } + + public TimeSpan Interval + { + get { return m_Interval; } + set { m_Interval = value; } + } + + private static MinHeap m_Heap = new MinHeap(); + private static System.Timers.Timer m_SystemTimer; + + public static System.Timers.Timer SystemTimer + { + get { return m_SystemTimer; } + set + { + if (m_SystemTimer != value) + { + if (m_SystemTimer != null) + m_SystemTimer.Stop(); + m_SystemTimer = value; + ChangedNextTick(); + } + } + } + + private static void ChangedNextTick() + { + ChangedNextTick(false); + } + + private static void ChangedNextTick(bool allowImmediate) + { + if (m_SystemTimer == null) + return; + + m_SystemTimer.Stop(); + + if (!m_Heap.IsEmpty) + { + int interval = (int)Math.Round(((Timer)m_Heap.Peek()).TimeUntilTick.TotalMilliseconds); + if (allowImmediate && interval <= 0) + { + Slice(); + } + else + { + if (interval <= 0) + interval = 1; + + m_SystemTimer.Interval = interval; + m_SystemTimer.Start(); + } + } + } + + public static void Slice() + { + int breakCount = 100; + List readd = new List(); + + while (!m_Heap.IsEmpty && ((Timer)m_Heap.Peek()).TimeUntilTick < TimeSpan.Zero) + { + if (breakCount-- <= 0) + break; + + Timer t = (Timer)m_Heap.Pop(); + + if (t != null && t.Running) + { + t.OnTick(); + + if (t.Running && (t.m_Count == 0 || (++t.m_Index) < t.m_Count)) + { + t.m_Next = DateTime.Now + t.m_Interval; + readd.Add(t); + } + else + { + t.Stop(); + } + } + } + + m_Heap.AddMultiple(readd); + + ChangedNextTick(); + } + + private class OneTimeTimer : Timer + { + private TimerCallback m_Call; + + public OneTimeTimer(TimeSpan d, TimerCallback call) + : base(d) + { + m_Call = call; + } + + protected override void OnTick() + { + m_Call(); + } + } + + public static Timer DelayedCallback(TimeSpan delay, TimerCallback call) + { + return new OneTimeTimer(delay, call); + } + + private class OneTimeTimerState : Timer + { + private TimerCallbackState m_Call; + private object m_State; + + public OneTimeTimerState(TimeSpan d, TimerCallbackState call, object state) + : base(d) + { + m_Call = call; + m_State = state; + } + + protected override void OnTick() + { + m_Call(m_State); + } + } + + public static Timer DelayedCallbackState(TimeSpan delay, TimerCallbackState call, object state) + { + return new OneTimeTimerState(delay, call, state); + } + } +} \ No newline at end of file diff --git a/Core/UOEntity.cs b/Core/UOEntity.cs new file mode 100644 index 0000000..2f7fba8 --- /dev/null +++ b/Core/UOEntity.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public interface IUOEntity + { + Serial Serial { get; } + ushort GraphicID { get; } + Point3D Position { get; set; } + IUOEntity Parent { get; set; } + } + public class UOEntity + { + private Serial m_Serial; + private Point3D m_Pos; + private ushort m_Hue; + private bool m_Deleted; + private Dictionary m_ContextMenu = new Dictionary(); + protected ObjectPropertyList m_ObjPropList = null; + + public ObjectPropertyList ObjPropList { get { return m_ObjPropList; } } + + public virtual void SaveState( BinaryWriter writer ) + { + writer.Write( (uint)m_Serial ); + writer.Write( (int)m_Pos.X ); + writer.Write( (int)m_Pos.Y ); + writer.Write( (int)m_Pos.Z ); + writer.Write( (ushort)m_Hue ); + } + + public UOEntity( BinaryReader reader, int version ) + { + m_Serial = reader.ReadUInt32(); + m_Pos = new Point3D( reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32() ); + m_Hue = reader.ReadUInt16(); + m_Deleted = false; + + m_ObjPropList = new ObjectPropertyList( this ); + } + + public virtual void AfterLoad() + { + } + + public UOEntity( Serial ser ) + { + m_ObjPropList = new ObjectPropertyList( this ); + + m_Serial = ser; + m_Deleted = false; + } + + public Serial Serial{ get{ return m_Serial; } } + + public virtual Point3D Position + { + get{ return m_Pos; } + set + { + if ( value != m_Pos ) + { + var oldPos = m_Pos; + m_Pos = value; + OnPositionChanging( oldPos ); + } + } + } + + public bool Deleted + { + get + { + return m_Deleted; + } + } + + public Dictionary ContextMenu + { + get { return m_ContextMenu; } + } + + public virtual ushort Hue + { + get{ return m_Hue; } + set{ m_Hue = value; } + } + + public virtual void Remove() + { + m_Deleted = true; + } + + public virtual void OnPositionChanging( Point3D oldPos ) + { + } + + public override int GetHashCode() + { + return m_Serial.GetHashCode(); + } + + public int OPLHash + { + get + { + if ( m_ObjPropList != null ) + return m_ObjPropList.Hash; + else + return 0; + } + set + { + if ( m_ObjPropList != null ) + m_ObjPropList.Hash = value; + } + } + + public bool ModifiedOPL { get { return m_ObjPropList.Customized; } } + + public void ReadPropertyList( PacketReader p ) + { + m_ObjPropList.Read( p ); + } + + /*public Packet BuildOPLPacket() + { + return m_ObjPropList.BuildPacket(); + }*/ + + public void OPLChanged() + { + //ClientCommunication.SendToClient( m_ObjPropList.BuildPacket() ); + ClientCommunication.SendToClient( new OPLInfo( Serial, OPLHash ) ); + } + } +} diff --git a/Core/Utility.cs b/Core/Utility.cs new file mode 100644 index 0000000..cf0e335 --- /dev/null +++ b/Core/Utility.cs @@ -0,0 +1,382 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; + +namespace Assistant +{ + public class Utility + { + public static string UintToEUO( uint Num ) + { + uint bSys = 26; + uint bNum, cnt, cnt2; + string res = ""; + char[] arr = new char[7]; + bNum = ( Num ^ 69 ) + 7; + cnt = 0; + do + { + arr[cnt] = (char)( ( bNum % bSys ) + 65 ); + if ( bNum < bSys ) break; + bNum = bNum / bSys; + cnt++; + + } while ( true ); + + return new string( arr ).Trim( '\0' ); + } + + public static ushort EUO2StealthType( string EUO ) + { + EUO = EUO.ToUpper(); + uint a = 0; + uint i = 1; + foreach ( var c in EUO ) + { + a += ( ( c - (uint)65 ) * i ); + i *= 26; + } + a = ( a - 7 ) ^ 0x45; + if ( a > 0xFFFF ) + return 0; + + return (ushort)a; + } + + public static uint EUO2StealthID( string EUO ) + { + EUO = EUO.ToUpper(); + uint ret = 0; + uint i = 1; + foreach ( var c in EUO ) + { + ret += ( c - (uint)65 ) * i; + i *= 26; + } + return ( ret - 7 ) ^ 0x45; + } + + + + private static Random m_Random = new Random(); + + public static int Random( int min, int max ) + { + return m_Random.Next( max - min + 1 ) + min; + } + + public static int Random( int num ) + { + return m_Random.Next( num ); + } + + public static bool InRange( IPoint2D from, IPoint2D to, int range ) + { + return ( to.X >= (from.X - range) ) + && ( to.X <= (from.X + range) ) + && ( to.Y >= (from.Y - range) ) + && ( to.Y <= (from.Y + range) ); + } + + public static int Distance( int fx, int fy, int tx, int ty ) + { + int xDelta = Math.Abs( fx - tx ); + int yDelta = Math.Abs( fy - ty ); + + return ( xDelta > yDelta ? xDelta : yDelta ); + } + + public static int Distance( IPoint2D from, IPoint2D to ) + { + int xDelta = Math.Abs( from.X - to.X ); + int yDelta = Math.Abs( from.Y - to.Y ); + + return ( xDelta > yDelta ? xDelta : yDelta ); + } + + public static double DistanceSqrt( IPoint2D from, IPoint2D to ) + { + float xDelta = Math.Abs( from.X - to.X ); + float yDelta = Math.Abs( from.Y - to.Y ); + + return Math.Sqrt( xDelta*xDelta + yDelta*yDelta ); + } + + public static void Offset( Direction d, ref int x, ref int y ) + { + switch ( d & Direction.Mask ) + { + case Direction.North: --y; break; + case Direction.South: ++y; break; + case Direction.West: --x; break; + case Direction.East: ++x; break; + case Direction.Right: ++x; --y; break; + case Direction.Left: --x; ++y; break; + case Direction.Down: ++x; ++y; break; + case Direction.Up: --x; --y; break; + } + } + + public static void FormatBuffer( TextWriter output, Stream input, int length ) + { + output.WriteLine( " 0 1 2 3 4 5 6 7 8 9 A B C D E F" ); + output.WriteLine( " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --" ); + + int byteIndex = 0; + + int whole = length >> 4; + int rem = length & 0xF; + + for ( int i = 0; i < whole; ++i, byteIndex += 16 ) + { + StringBuilder bytes = new StringBuilder( 49 ); + StringBuilder chars = new StringBuilder( 16 ); + + for ( int j = 0; j < 16; ++j ) + { + int c = input.ReadByte(); + + bytes.Append( c.ToString( "X2" ) ); + + if ( j != 7 ) + { + bytes.Append( ' ' ); + } + else + { + bytes.Append( " " ); + } + + if ( c >= 0x20 && c < 0x80 ) + { + chars.Append( (char)c ); + } + else + { + chars.Append( '.' ); + } + } + + output.Write( byteIndex.ToString( "X4" ) ); + output.Write( " " ); + output.Write( bytes.ToString() ); + output.Write( " " ); + output.WriteLine( chars.ToString() ); + } + + if ( rem != 0 ) + { + StringBuilder bytes = new StringBuilder( 49 ); + StringBuilder chars = new StringBuilder( rem ); + + for ( int j = 0; j < 16; ++j ) + { + if ( j < rem ) + { + int c = input.ReadByte(); + + bytes.Append( c.ToString( "X2" ) ); + + if ( j != 7 ) + { + bytes.Append( ' ' ); + } + else + { + bytes.Append( " " ); + } + + if ( c >= 0x20 && c < 0x80 ) + { + chars.Append( (char)c ); + } + else + { + chars.Append( '.' ); + } + } + else + { + bytes.Append( " " ); + } + } + + output.Write( byteIndex.ToString( "X4" ) ); + output.Write( " " ); + output.Write( bytes.ToString() ); + if ( rem <= 8 ) + output.Write( " " ); + else + output.Write( " " ); + output.WriteLine( chars.ToString() ); + } + } + + public static unsafe void FormatBuffer( TextWriter output, byte *buff, int length ) + { + output.WriteLine( " 0 1 2 3 4 5 6 7 8 9 A B C D E F" ); + output.WriteLine( " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --" ); + + int byteIndex = 0; + + int whole = length >> 4; + int rem = length & 0xF; + + for ( int i = 0; i < whole; ++i, byteIndex += 16 ) + { + StringBuilder bytes = new StringBuilder( 49 ); + StringBuilder chars = new StringBuilder( 16 ); + + for ( int j = 0; j < 16; ++j ) + { + int c = *buff++; + + bytes.Append( c.ToString( "X2" ) ); + + if ( j != 7 ) + { + bytes.Append( ' ' ); + } + else + { + bytes.Append( " " ); + } + + if ( c >= 0x20 && c < 0x80 ) + { + chars.Append( (char)c ); + } + else + { + chars.Append( '.' ); + } + } + + output.Write( byteIndex.ToString( "X4" ) ); + output.Write( " " ); + output.Write( bytes.ToString() ); + output.Write( " " ); + output.WriteLine( chars.ToString() ); + } + + if ( rem != 0 ) + { + StringBuilder bytes = new StringBuilder( 49 ); + StringBuilder chars = new StringBuilder( rem ); + + for ( int j = 0; j < 16; ++j ) + { + if ( j < rem ) + { + int c = *buff++; + + bytes.Append( c.ToString( "X2" ) ); + + if ( j != 7 ) + { + bytes.Append( ' ' ); + } + else + { + bytes.Append( " " ); + } + + if ( c >= 0x20 && c < 0x80 ) + { + chars.Append( (char)c ); + } + else + { + chars.Append( '.' ); + } + } + else + { + bytes.Append( " " ); + } + } + + output.Write( byteIndex.ToString( "X4" ) ); + output.Write( " " ); + output.Write( bytes.ToString() ); + if ( rem <= 8 ) + output.Write( " " ); + else + output.Write( " " ); + output.WriteLine( chars.ToString() ); + } + } + + private static char[] pathChars = new char[]{ '\\', '/' }; + public static string PathDisplayStr( string path, int maxLen ) + { + if ( path == null || path.Length <= maxLen || path.Length < 5 ) + return path; + + int first = (maxLen - 3) / 2; + int last = path.LastIndexOfAny( pathChars ); + if ( last == -1 || last < maxLen / 4 ) + last = path.Length - first; + first = maxLen - last - 3; + if ( first < 0 ) + first = 1; + if ( last < first ) + last = first; + + return String.Format( "{0}...{1}", path.Substring( 0, first ), path.Substring( last ) ); + } + + public static string FormatSize( long size ) + { + if ( size < 1024 ) // 1 K + return String.Format( "{0:#,##0} B", size ); + else if ( size < 1048576 ) // 1 M + return String.Format( "{0:#,###.0} KB", size / 1024.0 ); + else + return String.Format( "{0:#,###.0} MB", size / 1048576.0 ); + } + + public static string FormatTime( int sec ) + { + int m = sec/60; + int h = m/60; + m = m%60; + return String.Format( "{0:#0}:{1:00}:{2:00}", h, m, sec%60 ); + } + + public static string FormatTimeMS( int ms ) + { + int s = ms/1000; + int m = s/60; + int h = m/60; + + ms = ms%1000; + s = s%60; + m = m%60; + + if ( h > 0 || m > 55 ) + return String.Format( "{0:#0}:{1:00}:{2:00}.{3:000}", h, m, s, ms ); + else + return String.Format( "{0:00}:{1:00}.{2:000}", m, s, ms ); + } + + public static int ToInt32( string str, int def ) + { + if ( str == null ) + return def; + + try + { + if ( str.Length > 2 && str.Substring( 0, 2 ).ToLower() == "0x" ) + return Convert.ToInt32( str.Substring( 2 ), 16 ); + else + return Convert.ToInt32( str ); + } + catch + { + return def; + } + } + } +} diff --git a/Core/World.cs b/Core/World.cs new file mode 100644 index 0000000..1b8a3bf --- /dev/null +++ b/Core/World.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public class World + { + private static Dictionary m_Items; + private static Dictionary m_Mobiles; + private static PlayerData m_Player; + private static string m_ShardName, m_PlayerName, m_AccountName; + private static Dictionary m_Servers; + + static World() + { + m_Servers = new Dictionary(); + m_Items = new Dictionary(); + m_Mobiles = new Dictionary(); + m_ShardName = "[None]"; + } + + internal static Dictionary Servers { get { return m_Servers; } } + internal static Dictionary Items { get { return m_Items; } } + internal static Dictionary Mobiles { get { return m_Mobiles; } } + + internal static IUOEntity FindEntity( Serial serial ) + { + Item it; + m_Items.TryGetValue( serial, out it ); + if(it != null) + return it; + Mobile m; + m_Mobiles.TryGetValue( serial, out m ); + return m; + } + + internal static Item FindItem( Serial serial ) + { + Item it; + m_Items.TryGetValue(serial, out it); + return it; + } + + internal static Mobile FindMobile( Serial serial ) + { + Mobile m; + m_Mobiles.TryGetValue(serial, out m); + return m; + } + + internal static List MobilesInRange( int range ) + { + List list = new List(); + + if ( World.Player == null ) + return list; + + foreach ( Mobile m in World.Mobiles.Values ) + { + if ( Utility.InRange( World.Player.Position, m.Position, World.Player.VisRange ) ) + list.Add( m ); + } + + return list; + } + + internal static List MobilesInRange() + { + if ( Player == null ) + return MobilesInRange( 18 ); + else + return MobilesInRange( Player.VisRange ); + } + + internal static void AddItem( Item item ) + { + m_Items[item.Serial] = item; + } + + internal static void AddMobile( Mobile mob ) + { + m_Mobiles[mob.Serial] = mob; + } + + internal static void RemoveMobile( Mobile mob ) + { + m_Mobiles.Remove( mob.Serial ); + } + + internal static void RemoveItem( Item item ) + { + m_Items.Remove( item.Serial ); + } + + internal static PlayerData Player + { + get{ return m_Player; } + set{ m_Player = value; } + } + + internal static string ShardName + { + get{ return m_ShardName; } + set{ m_ShardName = value; } + } + + internal static string OrigPlayerName + { + get { return m_PlayerName; } + set { m_PlayerName = value; } + } + + internal static string AccountName + { + get { return m_AccountName; } + set { m_AccountName = value; } + } + } +} + diff --git a/Engine.cs b/Engine.cs new file mode 100644 index 0000000..31a57a2 --- /dev/null +++ b/Engine.cs @@ -0,0 +1,83 @@ +using CEasyUO; +using CUO_API; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Assistant +{ + public class Engine + { + + public Engine() + { + + } + public static ClientVersions ClientVersion { get; private set; } + + public static bool UseNewMobileIncoming => ClientVersion >= ClientVersions.CV_70331; + + public static bool UsePostHSChanges => ClientVersion >= ClientVersions.CV_7090; + + public static bool UsePostSAChanges => ClientVersion >= ClientVersions.CV_7000; + + public static bool UsePostKRPackets => ClientVersion >= ClientVersions.CV_6017; + private static string _rootPath = null; + public static string RootPath => _rootPath ?? ( _rootPath = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location ) ); + public static string UOFilePath { get; internal set; } + public static bool IsInstalled { get; internal set; } + + public static unsafe void Install( PluginHeader* header ) + { + Console.WriteLine( "Install Invoked CEasyUO" ); + try + { + ClientVersion = (ClientVersions)header->ClientVersion; + if ( !ClientCommunication.InstallHooks( header ) ) + { + System.Diagnostics.Process.GetCurrentProcess().Kill(); + return; + } + UOFilePath = Marshal.GetDelegateForFunctionPointer( header->GetUOFilePath )(); + Ultima.Files.SetMulPath( UOFilePath ); + Ultima.Multis.PostHSFormat = UsePostHSChanges; + Thread t = new Thread( () => + { + Thread.CurrentThread.Name = "EasyUO Main Thread"; + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault( false ); + Application.Run( new CEasyUOMainForm() ); + } ); + t.SetApartmentState( ApartmentState.STA ); + PacketHandlers.Initialize(); + // Targeting.Initialize(); + Spell.Initialize(); EUOVars.Initialize(); + t.IsBackground = true; + + t.Start(); + IsInstalled = true; + } + catch (Exception e) + { + Debugger.Break(); + Console.WriteLine( e.Message ); + } + + + + } + + + internal static void LogCrash( Exception e ) + { + } + } +} diff --git a/Network/ClientCommunication.cs b/Network/ClientCommunication.cs new file mode 100644 index 0000000..18badf8 --- /dev/null +++ b/Network/ClientCommunication.cs @@ -0,0 +1,196 @@ +using CUO_API; +using System; +using System.Diagnostics; +using System.Net; +using System.Runtime.InteropServices; + +namespace Assistant +{ + public unsafe sealed class ClientCommunication + { + public static DateTime ConnectionStart { get; private set; } + public static IPAddress LastConnection { get; } + + public static IntPtr ClientWindow { get; private set; } = IntPtr.Zero; + + private static OnPacketSendRecv _sendToClient, _sendToServer, _recv, _send; + private static OnGetPacketLength _getPacketLength; + private static OnGetPlayerPosition _getPlayerPosition; + private static OnCastSpell _castSpell; + private static OnGetStaticImage _getStaticImage; + + private static OnHotkey _onHotkeyPressed; + private static OnMouse _onMouse; + private static OnUpdatePlayerPosition _onUpdatePlayerPosition; + private static OnClientClose _onClientClose; + private static OnInitialize _onInitialize; + private static OnConnected _onConnected; + private static OnDisconnected _onDisconnected; + private static OnFocusGained _onFocusGained; + private static OnFocusLost _onFocusLost; + + internal static bool InstallHooks( PluginHeader* header ) + { + _sendToClient = Marshal.GetDelegateForFunctionPointer( header->Recv ); + _sendToServer = Marshal.GetDelegateForFunctionPointer( header->Send ); + _getPacketLength = Marshal.GetDelegateForFunctionPointer( header->GetPacketLength ); + _getPlayerPosition = Marshal.GetDelegateForFunctionPointer( header->GetPlayerPosition ); + _castSpell = Marshal.GetDelegateForFunctionPointer( header->CastSpell ); + _getStaticImage = Marshal.GetDelegateForFunctionPointer( header->GetStaticImage ); + + ClientWindow = header->HWND; + + _recv = OnRecv; + _send = OnSend; + _onHotkeyPressed = OnHotKeyHandler; + _onMouse = OnMouseHandler; + _onUpdatePlayerPosition = OnPlayerPositionChanged; + _onClientClose = OnClientClosing; + _onInitialize = OnInitialize; + _onConnected = OnConnected; + _onDisconnected = OnDisconnected; + // _onFocusGained = OnFocusGained; + // _onFocusLost = OnFocusLost; + + header->OnRecv = Marshal.GetFunctionPointerForDelegate( _recv ); + header->OnSend = Marshal.GetFunctionPointerForDelegate( _send ); + header->OnHotkeyPressed = Marshal.GetFunctionPointerForDelegate( _onHotkeyPressed ); + header->OnMouse = Marshal.GetFunctionPointerForDelegate( _onMouse ); + header->OnPlayerPositionChanged = Marshal.GetFunctionPointerForDelegate( _onUpdatePlayerPosition ); + header->OnClientClosing = Marshal.GetFunctionPointerForDelegate( _onClientClose ); + header->OnInitialize = Marshal.GetFunctionPointerForDelegate( _onInitialize ); + header->OnConnected = Marshal.GetFunctionPointerForDelegate( _onConnected ); + header->OnDisconnected = Marshal.GetFunctionPointerForDelegate( _onDisconnected ); + // header->OnFocusGained = Marshal.GetFunctionPointerForDelegate( _onFocusGained ); + // header->OnFocusLost = Marshal.GetFunctionPointerForDelegate( _onFocusLost ); + + return true; + } + + private static void OnClientClosing() + { + var last = Console.BackgroundColor; + var lastFore = Console.ForegroundColor; + Console.BackgroundColor = ConsoleColor.Red; + Console.ForegroundColor = ConsoleColor.Black; + Console.WriteLine( "Closing EasyUO instance" ); + Console.BackgroundColor = last; + Console.ForegroundColor = lastFore; + + } + + private static void OnInitialize() + { + var last = Console.BackgroundColor; + var lastFore = Console.ForegroundColor; + Console.BackgroundColor = ConsoleColor.Green; + Console.ForegroundColor = ConsoleColor.Black; + Console.WriteLine( "Initialized EasyUO instance" ); + Console.BackgroundColor = last; + Console.ForegroundColor = lastFore; + } + + private static void OnConnected() + { + ConnectionStart = DateTime.Now; + try + { + } + catch + { + } + } + + private static void OnDisconnected() + { + PacketHandlers.Party.Clear(); + + + + World.Player = null; + World.Items.Clear(); + World.Mobiles.Clear(); + ActionQueue.Stop(); + + BuffsTimer.Stop(); + + PacketHandlers.Party.Clear(); + PacketHandlers.IgnoreGumps.Clear(); + } + + + + + private static bool OnHotKeyHandler( int key, int mod, bool ispressed ) + { + if ( ispressed ) + { + // bool code = HotKey.OnKeyDown( (int)( key | mod ) ); + + return true;// code; + } + + return true; + } + + private static void OnMouseHandler( int button, int wheel ) + { + if ( button > 4 ) + button = 3; + else if ( button > 3 ) + button = 2; + else if ( button > 2 ) + button = 2; + else if ( button > 1 ) + button = 1; + + //HotKey.OnMouse( button, wheel ); + } + + private static void OnPlayerPositionChanged( int x, int y, int z ) + { + if(World.Player != null) + World.Player.Position = new Point3D( x, y, z ); + } + + private static bool OnRecv( byte[] data, int length ) + { + fixed ( byte* ptr = data ) + { + PacketReader p = new PacketReader( ptr, length, PacketsTable.GetPacketLength( data[0] ) < 0 ); + Packet packet = new Packet( data, length, p.DynamicLength ); + + return !PacketHandler.OnServerPacket( p.PacketID, p, packet ); + } + } + + private static bool OnSend( byte[] data, int length ) + { + fixed ( byte* ptr = data ) + { + PacketReader p = new PacketReader( ptr, length, PacketsTable.GetPacketLength( data[0] ) < 0 ); + Packet packet = new Packet( data, length, p.DynamicLength ); + + return !PacketHandler.OnClientPacket( p.PacketID, p, packet ); + } + } + + public static void CastSpell( int idx ) => _castSpell?.Invoke( idx ); + + public static bool GetPlayerPosition( out int x, out int y, out int z ) + => _getPlayerPosition( out x, out y, out z ); + + internal static void SendToServer( Packet p ) + { + var len = p.Length; + _sendToServer?.Invoke( p.Compile(), (int)len ); + } + + internal static void SendToClient( Packet p ) + { + var len = p.Length; + _sendToClient?.Invoke( p.Compile(), (int)len ); + } + } + +} diff --git a/Network/Handlers.cs b/Network/Handlers.cs new file mode 100644 index 0000000..fedbe69 --- /dev/null +++ b/Network/Handlers.cs @@ -0,0 +1,2458 @@ +using System; +using System.IO; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using ContainerLabels = Assistant.Core.ContainerLabels; +using OverheadMessages = Assistant.Core.OverheadMessages; +using Assistant.Core; + +namespace Assistant +{ + public class PacketHandlers + { + private static List m_IgnoreGumps = new List(); + public static List IgnoreGumps { get { return m_IgnoreGumps; } } + + public static void Initialize() + { + //Client -> Server handlers + PacketHandler.RegisterClientToServerViewer(0x00, new PacketViewerCallback(CreateCharacter)); + //PacketHandler.RegisterClientToServerViewer(0x01, new PacketViewerCallback(Disconnect)); + PacketHandler.RegisterClientToServerFilter(0x02, new PacketFilterCallback(MovementRequest)); + PacketHandler.RegisterClientToServerFilter(0x05, new PacketFilterCallback(AttackRequest)); + PacketHandler.RegisterClientToServerViewer(0x06, new PacketViewerCallback(ClientDoubleClick)); + PacketHandler.RegisterClientToServerViewer(0x07, new PacketViewerCallback(LiftRequest)); + PacketHandler.RegisterClientToServerViewer(0x08, new PacketViewerCallback(DropRequest)); + PacketHandler.RegisterClientToServerViewer(0x09, new PacketViewerCallback(ClientSingleClick)); + PacketHandler.RegisterClientToServerViewer(0x12, new PacketViewerCallback(ClientTextCommand)); + PacketHandler.RegisterClientToServerViewer(0x13, new PacketViewerCallback(EquipRequest)); + // 0x29 - UOKR confirm drop. 0 bytes payload (just a single byte, 0x29, no length or data) + PacketHandler.RegisterClientToServerViewer(0x3A, new PacketViewerCallback(SetSkillLock)); + PacketHandler.RegisterClientToServerViewer(0x5D, new PacketViewerCallback(PlayCharacter)); + PacketHandler.RegisterClientToServerViewer(0x7D, new PacketViewerCallback(MenuResponse)); + PacketHandler.RegisterClientToServerFilter(0x91, new PacketFilterCallback(GameLogin)); + PacketHandler.RegisterClientToServerViewer(0x95, new PacketViewerCallback(HueResponse)); + PacketHandler.RegisterClientToServerViewer(0xA0, new PacketViewerCallback(PlayServer)); + PacketHandler.RegisterClientToServerViewer(0xB1, new PacketViewerCallback(ClientGumpResponse)); + PacketHandler.RegisterClientToServerFilter(0xBF, new PacketFilterCallback(ExtendedClientCommand)); + //PacketHandler.RegisterClientToServerViewer( 0xD6, new PacketViewerCallback( BatchQueryProperties ) ); + PacketHandler.RegisterClientToServerViewer(0xD7, new PacketViewerCallback(ClientEncodedPacket)); + PacketHandler.RegisterClientToServerViewer(0xF8, new PacketViewerCallback(CreateCharacter)); + + //Server -> Client handlers + //PacketHandler.RegisterServerToClientViewer(0x0B, new PacketViewerCallback(Damage)); + PacketHandler.RegisterServerToClientViewer(0x11, new PacketViewerCallback(MobileStatus)); + PacketHandler.RegisterServerToClientViewer(0x17, new PacketViewerCallback(NewMobileStatus)); + PacketHandler.RegisterServerToClientViewer(0x1A, new PacketViewerCallback(WorldItem)); + PacketHandler.RegisterServerToClientViewer(0x1B, new PacketViewerCallback(LoginConfirm)); + PacketHandler.RegisterServerToClientFilter(0x1C, new PacketFilterCallback(AsciiSpeech)); + PacketHandler.RegisterServerToClientViewer(0x1D, new PacketViewerCallback(RemoveObject)); + PacketHandler.RegisterServerToClientFilter(0x20, new PacketFilterCallback(MobileUpdate)); + PacketHandler.RegisterServerToClientViewer(0x24, new PacketViewerCallback(BeginContainerContent)); + PacketHandler.RegisterServerToClientFilter(0x25, new PacketFilterCallback(ContainerContentUpdate)); + PacketHandler.RegisterServerToClientViewer(0x27, new PacketViewerCallback(LiftReject)); + PacketHandler.RegisterServerToClientViewer(0x2D, new PacketViewerCallback(MobileStatInfo)); + PacketHandler.RegisterServerToClientFilter(0x2E, new PacketFilterCallback(EquipmentUpdate)); + PacketHandler.RegisterServerToClientViewer(0x3A, new PacketViewerCallback(Skills)); + PacketHandler.RegisterServerToClientFilter(0x3C, new PacketFilterCallback(ContainerContent)); + PacketHandler.RegisterServerToClientViewer(0x4E, new PacketViewerCallback(PersonalLight)); + PacketHandler.RegisterServerToClientViewer(0x4F, new PacketViewerCallback(GlobalLight)); + PacketHandler.RegisterServerToClientViewer(0x6F, new PacketViewerCallback(TradeRequest)); + PacketHandler.RegisterServerToClientViewer(0x72, new PacketViewerCallback(ServerSetWarMode)); + PacketHandler.RegisterServerToClientViewer(0x73, new PacketViewerCallback(PingResponse)); + PacketHandler.RegisterServerToClientViewer(0x76, new PacketViewerCallback(ServerChange)); + PacketHandler.RegisterServerToClientFilter(0x77, new PacketFilterCallback(MobileMoving)); + PacketHandler.RegisterServerToClientFilter(0x78, new PacketFilterCallback(MobileIncoming)); + PacketHandler.RegisterServerToClientViewer(0x7C, new PacketViewerCallback(SendMenu)); + PacketHandler.RegisterServerToClientFilter(0x8C, new PacketFilterCallback(ServerAddress)); + PacketHandler.RegisterServerToClientViewer(0xA1, new PacketViewerCallback(HitsUpdate)); + PacketHandler.RegisterServerToClientViewer(0xA2, new PacketViewerCallback(ManaUpdate)); + PacketHandler.RegisterServerToClientViewer(0xA3, new PacketViewerCallback(StamUpdate)); + PacketHandler.RegisterServerToClientViewer(0xA8, new PacketViewerCallback(ServerList)); + PacketHandler.RegisterServerToClientViewer(0xAB, new PacketViewerCallback(DisplayStringQuery)); + PacketHandler.RegisterServerToClientViewer(0xAF, new PacketViewerCallback(DeathAnimation)); + PacketHandler.RegisterServerToClientFilter(0xAE, new PacketFilterCallback(UnicodeSpeech)); + PacketHandler.RegisterServerToClientViewer(0xB0, new PacketViewerCallback(SendGump)); + PacketHandler.RegisterServerToClientViewer(0xB9, new PacketViewerCallback(Features)); + PacketHandler.RegisterServerToClientViewer(0xBC, new PacketViewerCallback(ChangeSeason)); + PacketHandler.RegisterServerToClientViewer(0xBF, new PacketViewerCallback(ExtendedPacket)); + PacketHandler.RegisterServerToClientFilter(0xC1, new PacketFilterCallback(OnLocalizedMessage)); + PacketHandler.RegisterServerToClientFilter(0xC8, new PacketFilterCallback(SetUpdateRange)); + PacketHandler.RegisterServerToClientFilter(0xCC, new PacketFilterCallback(OnLocalizedMessageAffix)); + PacketHandler.RegisterServerToClientViewer(0xD6, new PacketViewerCallback(EncodedPacket));//0xD6 "encoded" packets + PacketHandler.RegisterServerToClientViewer(0xD8, new PacketViewerCallback(CustomHouseInfo)); + //PacketHandler.RegisterServerToClientFilter( 0xDC, new PacketFilterCallback( ServOPLHash ) ); + PacketHandler.RegisterServerToClientViewer(0xDD, new PacketViewerCallback(CompressedGump)); + PacketHandler.RegisterServerToClientViewer(0xF0, new PacketViewerCallback(RunUOProtocolExtention)); // Special RunUO protocol extentions (for KUOC/Razor) + + PacketHandler.RegisterServerToClientViewer(0xF3, new PacketViewerCallback(SAWorldItem)); + + PacketHandler.RegisterServerToClientViewer(0x2C, new PacketViewerCallback(ResurrectionGump)); + + PacketHandler.RegisterServerToClientViewer(0xDF, new PacketViewerCallback(BuffDebuff)); + } + + private static void DisplayStringQuery(PacketReader p, PacketHandlerEventArgs args) + { + // See also Packets.cs: StringQueryResponse + /*if ( MacroManager.AcceptActions ) + { + int serial = p.ReadInt32(); + byte type = p.ReadByte(); + byte index = p.ReadByte(); + + MacroManager.Action( new WaitForTextEntryAction( serial, type, index ) ); + }*/ + } + + private static void SetUpdateRange(Packet p, PacketHandlerEventArgs args) + { + if (World.Player != null) + World.Player.VisRange = p.ReadByte(); + } + + private static void EncodedPacket(PacketReader p, PacketHandlerEventArgs args) + { + /*ushort id = p.ReadUInt16(); + + switch ( id ) + { + case 1: // object property list + { + Serial s = p.ReadUInt32(); + + if ( s.IsItem ) + { + Item item = World.FindItem( s ); + if ( item == null ) + World.AddItem( item=new Item( s ) ); + + item.ReadPropertyList( p ); + if ( item.ModifiedOPL ) + { + args.Block = true; + ClientCommunication.SendToClient( item.ObjPropList.BuildPacket() ); + } + } + else if ( s.IsMobile ) + { + Mobile m = World.FindMobile( s ); + if ( m == null ) + World.AddMobile( m=new Mobile( s ) ); + + m.ReadPropertyList( p ); + if ( m.ModifiedOPL ) + { + args.Block = true; + ClientCommunication.SendToClient( m.ObjPropList.BuildPacket() ); + } + } + break; + } + }*/ + } + + private static void ServOPLHash(Packet p, PacketHandlerEventArgs args) + { + /*Serial s = p.ReadUInt32(); + int hash = p.ReadInt32(); + + if ( s.IsItem ) + { + Item item = World.FindItem( s ); + if ( item != null && item.OPLHash != hash ) + { + item.OPLHash = hash; + p.Seek( -4, SeekOrigin.Current ); + p.Write( (uint)item.OPLHash ); + } + } + else if ( s.IsMobile ) + { + Mobile m = World.FindMobile( s ); + if ( m != null && m.OPLHash != hash ) + { + m.OPLHash = hash; + p.Seek( -4, SeekOrigin.Current ); + p.Write( (uint)m.OPLHash ); + } + }*/ + } + + private static void ClientSingleClick(PacketReader p, PacketHandlerEventArgs args) + { + Serial ser = p.ReadUInt32(); + + // if you modify this, don't forget to modify the allnames hotkey + if (Config.GetBool("LastTargTextFlags")) + { + Mobile m = World.FindMobile(ser); + if (m != null) + Targeting.CheckTextFlags(m); + } + + } + + private static void ClientDoubleClick(PacketReader p, PacketHandlerEventArgs args) + { + Serial ser = p.ReadUInt32(); + if (Config.GetBool("BlockDismount") && World.Player != null && ser == World.Player.Serial && World.Player.Warmode && World.Player.GetItemOnLayer(Layer.Mount) != null) + { // mount layer = 0x19 + args.Block = true; + return; + } +PlayerData.DoubleClick(ser, false); + + + } + + private static void DeathAnimation(PacketReader p, PacketHandlerEventArgs args) + { + Serial killed = p.ReadUInt32(); + + } + + private static void ExtendedClientCommand(Packet p, PacketHandlerEventArgs args) + { + ushort ext = p.ReadUInt16(); + switch (ext) + { + case 0x10: // query object properties + { + break; + } + case 0x15: // context menu response + { + UOEntity ent = null; + Serial ser = p.ReadUInt32(); + ushort idx = p.ReadUInt16(); + + if (ser.IsMobile) + ent = World.FindMobile(ser); + else if (ser.IsItem) + ent = World.FindItem(ser); + + if (ent != null && ent.ContextMenu != null) + { + ushort menu;// = (ushort)ent.ContextMenu[idx]; + // if (ent.ContextMenu.TryGetValue(idx, out menu) && menu != 0 && MacroManager.AcceptActions) + // MacroManager.Action(new ContextMenuAction(ent, idx, menu)); + } + + break; + } + case 0x1C:// cast spell + { + Serial ser = Serial.MinusOne; + if (p.ReadUInt16() == 1) + ser = p.ReadUInt32(); + ushort sid = p.ReadUInt16(); + Spell s = Spell.Get(sid); + if (s != null) + { + if(World.Player != null) + World.Player.LastSpell = sid; + s.OnCast(p); + args.Block = true; + + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new ExtCastSpellAction(s, ser)); + } + break; + } + case 0x24: + { + // for the cheatx0r part 2... anything outside this range indicates some haxing, just hide it with 0x30s + byte b = p.ReadByte(); + if (b < 0x25 || b >= 0x5E + 0x25) + { + p.Seek(-1, SeekOrigin.Current); + p.Write((byte)0x30); + } + //using ( StreamWriter w = new StreamWriter( "bf24.txt", true ) ) + // w.WriteLine( "{0} : 0x{1:X2}", Engine.MistedDateTime.ToString( "HH:mm:ss.ffff" ), b ); + break; + } + } + } + + private static void ClientTextCommand(PacketReader p, PacketHandlerEventArgs args) + { + int type = p.ReadByte(); + string command = p.ReadString(); + + switch (type) + { + case 0x24: // Use skill + { + int skillIndex; + + try { skillIndex = Convert.ToInt32(command.Split(' ')[0]); } + catch { break; } + + if (World.Player != null) + World.Player.LastSkill = skillIndex; + + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new UseSkillAction(skillIndex)); + + // if (skillIndex == (int)SkillName.Stealth && !World.Player.Visible) + // StealthSteps.Hide(); + + SkillTimer.Start(); + break; + } + case 0x27: // Cast spell from book + { + try + { + string[] split = command.Split(' '); + + if (split.Length > 0) + { + ushort spellID = Convert.ToUInt16(split[0]); + Serial serial = Convert.ToUInt32(split.Length > 1 ? Utility.ToInt32(split[1], -1) : -1); + Spell s = Spell.Get(spellID); + if (s != null) + { + s.OnCast(spellID); + args.Block = true; + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new BookCastSpellAction(s, serial)); + } + } + } + catch + { + } + break; + } + case 0x56: // Cast spell from macro + { + try + { + ushort spellID = Convert.ToUInt16(command); + Spell s = Spell.Get(spellID); + if (s != null) + { + s.OnCast(spellID); + args.Block = true; + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new MacroCastSpellAction(s)); + } + } + catch + { + } + break; + } + } + } + + public static DateTime PlayCharTime = DateTime.MinValue; + + private static void CreateCharacter(PacketReader p, PacketHandlerEventArgs args) + { + p.Seek(1 + 4 + 4 + 1, SeekOrigin.Begin); // skip begining crap + World.OrigPlayerName = p.ReadStringSafe(30); + + PlayCharTime = DateTime.UtcNow; + + + } + + private static void PlayCharacter(PacketReader p, PacketHandlerEventArgs args) + { + p.ReadUInt32(); //0xedededed + World.OrigPlayerName = p.ReadStringSafe(30); + + PlayCharTime = DateTime.UtcNow; + + + //ClientCommunication.TranslateLogin( World.OrigPlayerName, World.ShardName ); + } + + private static void ServerList(PacketReader p, PacketHandlerEventArgs args) + { + p.ReadByte(); //unknown + ushort numServers = p.ReadUInt16(); + + for (int i = 0; i < numServers; ++i) + { + ushort num = p.ReadUInt16(); + World.Servers[num] = p.ReadString(32); + p.ReadByte(); // full % + p.ReadSByte(); // time zone + p.ReadUInt32(); // ip + } + } + + private static void PlayServer(PacketReader p, PacketHandlerEventArgs args) + { + ushort index = p.ReadUInt16(); + string name; + if (World.Servers.TryGetValue(index, out name) && !string.IsNullOrEmpty(name)) + World.ShardName = name; + else + World.ShardName = "[Unknown]"; + } + + private static void LiftRequest(PacketReader p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + ushort amount = p.ReadUInt16(); + + Item item = World.FindItem(serial); + ushort iid = 0; + + if (item != null) + iid = item.ItemID.Value; + + if (Config.GetBool("QueueActions")) + { + if (item == null) + { + World.AddItem(item = new Item(serial)); + item.Amount = amount; + } + + DragDropManager.Drag(item, amount, true); + //ClientCommunication.SendToClient( new RemoveObject( serial ) ); // remove the object from the client view + args.Block = true; + } + + //if (Macros.MacroManager.AcceptActions) + { + // MacroManager.Action(new LiftAction(serial, amount, iid)); + //MacroManager.Action( new PauseAction( TimeSpan.FromMilliseconds( Config.GetInt( "ObjectDelay" ) ) ) ); + } + } + + private static void LiftReject(PacketReader p, PacketHandlerEventArgs args) + { + /* + if ( ActionQueue.FilterLiftReject() ) + args.Block = true; + */ + int reason = p.ReadByte(); + + if (!DragDropManager.LiftReject()) + args.Block = true; + //MacroManager.PlayError( MacroError.LiftRej ); + } + + private static void EquipRequest(PacketReader p, PacketHandlerEventArgs args) + { + Serial iser = p.ReadUInt32(); // item being dropped serial + Layer layer = (Layer)p.ReadByte(); + Serial mser = p.ReadUInt32(); + + Item item = World.FindItem(iser); + + /* if (MacroManager.AcceptActions) + { + if (layer == Layer.Invalid || layer > Layer.LastValid) + { + if (item != null) + { + layer = item.Layer; + if (layer == Layer.Invalid || layer > Layer.LastValid) + layer = (Layer)item.ItemID.ItemData.Quality; + } + } + + if (layer > Layer.Invalid && layer <= Layer.LastUserValid) + MacroManager.Action(new DropAction(mser, Point3D.Zero, layer)); + }*/ + + if (item == null) + return; + + Mobile m = World.FindMobile(mser); + if (m == null) + return; + + if (Config.GetBool("QueueActions")) + args.Block = DragDropManager.Drop(item, m, layer); + } + + private static void DropRequest(PacketReader p, PacketHandlerEventArgs args) + { + Serial iser = p.ReadUInt32(); + int x = p.ReadInt16(); + int y = p.ReadInt16(); + int z = p.ReadSByte(); + if (Engine.UsePostKRPackets) + /* grid num */ + p.ReadByte(); + Point3D newPos = new Point3D(x, y, z); + Serial dser = p.ReadUInt32(); + + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new DropAction(dser, newPos)); + + Item i = World.FindItem(iser); + if (i == null) + return; + + Item dest = World.FindItem(dser); + if (dest != null && dest.IsContainer && World.Player != null && (dest.IsChildOf(World.Player.Backpack) || dest.IsChildOf(World.Player.Quiver))) + i.IsNew = true; + + if (Config.GetBool("QueueActions")) + args.Block = DragDropManager.Drop(i, dser, newPos); + } + + private static void MovementRequest(Packet p, PacketHandlerEventArgs args) + { + if (World.Player != null) + { + Direction dir = (Direction)p.ReadByte(); + byte seq = p.ReadByte(); + + World.Player.Direction = (dir & Direction.Mask); + World.Player.WalkSequence = seq; + //WalkAction.LastWalkTime = DateTime.UtcNow; + // if (MacroManager.AcceptActions) + // MacroManager.Action(new WalkAction(dir)); + } + } + + private static void ContainerContentUpdate(Packet p, PacketHandlerEventArgs args) + { + // This function will ignore the item if the container item has not been sent to the client yet. + // We can do this because we can't really count on getting all of the container info anyway. + // (So we'd need to request the container be updated, so why bother with the extra stuff required to find the container once its been sent?) + Serial serial = p.ReadUInt32(); + ushort itemid = p.ReadUInt16(); + itemid = (ushort)(itemid + p.ReadSByte()); // signed, itemID offset + ushort amount = p.ReadUInt16(); + if (amount == 0) + amount = 1; + Point3D pos = new Point3D(p.ReadUInt16(), p.ReadUInt16(), 0); + byte gridPos = 0; + if (Engine.UsePostKRPackets) + gridPos = p.ReadByte(); + Serial cser = p.ReadUInt32(); + ushort hue = p.ReadUInt16(); + + Item i = World.FindItem(serial); + if (i == null) + { + if (!serial.IsItem) + return; + + World.AddItem(i = new Item(serial)); + i.IsNew = i.AutoStack = true; + } + else + { + i.CancelRemove(); + } + + if (serial != DragDropManager.Pending) + { + if (!DragDropManager.EndHolding(serial)) + return; + } + + i.ItemID = itemid; + i.Amount = amount; + i.Position = pos; + i.GridNum = gridPos; + i.Hue = hue; + + + + i.Container = cser; + if (i.IsNew) + Item.UpdateContainers(); + + } + + private static void BeginContainerContent(PacketReader p, PacketHandlerEventArgs args) + { + Serial ser = p.ReadUInt32(); + if (!ser.IsItem) + return; + Item item = World.FindItem(ser); + if (item != null) + { + if (m_IgnoreGumps.Contains(item)) + { + m_IgnoreGumps.Remove(item); + args.Block = true; + } + World.Player.LastContainer = item; + } + else + { + World.AddItem(new Item(ser)); + Item.UpdateContainers(); + item = World.FindItem( ser ); + World.Player.LastContainer = item; + } + World.Player.LastContainerOpenedAt = DateTime.UtcNow; + World.Player.LastContainerGumpGraphic = p.ReadUInt16(); + } + + private static void ContainerContent(Packet p, PacketHandlerEventArgs args) + { + int count = p.ReadUInt16(); + + for (int i = 0; i < count; i++) + { + Serial serial = p.ReadUInt32(); + // serial is purposely not checked to be valid, sometimes buy lists dont have "valid" item serials (and we are okay with that). + Item item = World.FindItem(serial); + if (item == null) + { + World.AddItem(item = new Item(serial)); + item.IsNew = true; + item.AutoStack = false; + } + else + { + item.CancelRemove(); + } + + if (!DragDropManager.EndHolding(serial)) + continue; + + item.ItemID = p.ReadUInt16(); + item.ItemID = (ushort)(item.ItemID + p.ReadSByte());// signed, itemID offset + item.Amount = p.ReadUInt16(); + if (item.Amount == 0) + item.Amount = 1; + item.Position = new Point3D(p.ReadUInt16(), p.ReadUInt16(), 0); + if (Engine.UsePostKRPackets) + item.GridNum = p.ReadByte(); + Serial cont = p.ReadUInt32(); + var container = World.FindItem( cont ); + + if ( i == 0 ) + { + if(container!= null) + { + container.ClearContents(); + + + } + + } + item.Hue = p.ReadUInt16(); + + + item.Container = cont; // must be done after hue is set (for counters) + + } + Item.UpdateContainers(); + } + + private static void EquipmentUpdate(Packet p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + + Item i = World.FindItem(serial); + bool isNew = false; + if (i == null) + { + World.AddItem(i = new Item(serial)); + isNew = true; + Item.UpdateContainers(); + } + else + { + i.CancelRemove(); + } + + if (!DragDropManager.EndHolding(serial)) + return; + + ushort iid = p.ReadUInt16(); + i.ItemID = (ushort)(iid + p.ReadSByte()); // signed, itemID offset + i.Layer = (Layer)p.ReadByte(); + Serial ser = p.ReadUInt32();// cont must be set after hue (for counters) + i.Hue = p.ReadUInt16(); + + i.Container = ser; + + int ltHue = Config.GetInt("LTHilight"); + if (ltHue != 0 && Targeting.IsLastTarget(i.Container as Mobile)) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((ushort)(ltHue & 0x3FFF)); + } + + if (i.Layer == Layer.Backpack && isNew && Config.GetBool("AutoSearch") && ser == World.Player.Serial) + { + m_IgnoreGumps.Add(i); + PlayerData.DoubleClick(i); + } + } + + private static void SetSkillLock(PacketReader p, PacketHandlerEventArgs args) + { + int i = p.ReadUInt16(); + + if (i >= 0 && i < Skill.Count) + { + Skill skill = World.Player.Skills[i]; + + skill.Lock = (LockType)p.ReadByte(); + } + } + + private static void Skills(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player == null || World.Player.Skills == null) + return; + + byte type = p.ReadByte(); + + switch (type) + { + case 0x02://list (with caps, 3.0.8 and up) + { + int i; + while ((i = p.ReadUInt16()) > 0) + { + if (i > 0 && i <= Skill.Count) + { + Skill skill = World.Player.Skills[i - 1]; + + if (skill == null) + continue; + + skill.FixedValue = p.ReadUInt16(); + skill.FixedBase = p.ReadUInt16(); + skill.Lock = (LockType)p.ReadByte(); + skill.FixedCap = p.ReadUInt16(); + if (!World.Player.SkillsSent) + skill.Delta = 0; + + } + else + { + p.Seek(7, SeekOrigin.Current); + } + } + + World.Player.SkillsSent = true; + break; + } + + case 0x00: // list (without caps, older clients) + { + int i; + while ((i = p.ReadUInt16()) > 0) + { + if (i > 0 && i <= Skill.Count) + { + Skill skill = World.Player.Skills[i - 1]; + + if (skill == null) + continue; + + skill.FixedValue = p.ReadUInt16(); + skill.FixedBase = p.ReadUInt16(); + skill.Lock = (LockType)p.ReadByte(); + skill.FixedCap = 100;//p.ReadUInt16(); + if (!World.Player.SkillsSent) + skill.Delta = 0; + + } + else + { + p.Seek(5, SeekOrigin.Current); + } + } + + World.Player.SkillsSent = true; + break; + } + + case 0xDF: //change (with cap, new clients) + { + int i = p.ReadUInt16(); + + if (i >= 0 && i < Skill.Count) + { + Skill skill = World.Player.Skills[i]; + + if (skill == null) + break; + + ushort old = skill.FixedBase; + skill.FixedValue = p.ReadUInt16(); + skill.FixedBase = p.ReadUInt16(); + skill.Lock = (LockType)p.ReadByte(); + skill.FixedCap = p.ReadUInt16(); + + + } + break; + } + + case 0xFF: //change (without cap, older clients) + { + int i = p.ReadUInt16(); + + if (i >= 0 && i < Skill.Count) + { + Skill skill = World.Player.Skills[i]; + + if (skill == null) + break; + + ushort old = skill.FixedBase; + skill.FixedValue = p.ReadUInt16(); + skill.FixedBase = p.ReadUInt16(); + skill.Lock = (LockType)p.ReadByte(); + skill.FixedCap = 100; + + } + break; + } + } + } + + private static void LoginConfirm(PacketReader p, PacketHandlerEventArgs args) + { + Console.WriteLine( "Login Confirm" ); + World.Items.Clear(); + World.Mobiles.Clear(); + + UseNewStatus = false; + + Serial serial = p.ReadUInt32(); + + PlayerData m = new PlayerData(serial); + m.Name = World.OrigPlayerName; + + Mobile test = World.FindMobile(serial); + if (test != null) + test.Remove(); + + World.AddMobile(World.Player = m); + + p.ReadUInt32(); // always 0? + m.Body = p.ReadUInt16(); + m.Position = new Point3D(p.ReadUInt16(), p.ReadUInt16(), p.ReadInt16()); + m.Direction = (Direction)p.ReadByte(); + + } + + private static void MobileMoving(Packet p, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile(p.ReadUInt32()); + + if (m != null) + { + m.Body = p.ReadUInt16(); + m.Position = new Point3D(p.ReadUInt16(), p.ReadUInt16(), p.ReadSByte()); + + if (World.Player != null && !Utility.InRange(World.Player.Position, m.Position, World.Player.VisRange)) + { + m.Remove(); + return; + } + + Targeting.CheckLastTargetRange(m); + + m.Direction = (Direction)p.ReadByte(); + m.Hue = p.ReadUInt16(); + int ltHue = Config.GetInt("LTHilight"); + if (ltHue != 0 && Targeting.IsLastTarget(m)) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((short)(ltHue | 0x8000)); + } + + bool wasPoisoned = m.Poisoned; + m.ProcessPacketFlags(p.ReadByte()); + byte oldNoto = m.Notoriety; + m.Notoriety = p.ReadByte(); + + + } + } + + private static readonly int[] HealthHues = new int[] { 428, 333, 37, 44, 49, 53, 158, 263, 368, 473, 578 }; + + private static void HitsUpdate(PacketReader p, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile(p.ReadUInt32()); + + if (m != null) + { + int oldPercent = (int)(m.Hits * 100 / (m.HitsMax == 0 ? (ushort)1 : m.HitsMax)); + + m.HitsMax = p.ReadUInt16(); + m.Hits = p.ReadUInt16(); + + } + } + + private static void StamUpdate(PacketReader p, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile(p.ReadUInt32()); + + if (m != null) + { + int oldPercent = (int)(m.Stam * 100 / (m.StamMax == 0 ? (ushort)1 : m.StamMax)); + + m.StamMax = p.ReadUInt16(); + m.Stam = p.ReadUInt16(); + + + + } + } + + private static void ManaUpdate(PacketReader p, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile(p.ReadUInt32()); + + if (m != null) + { + int oldPercent = (int)(m.Mana * 100 / (m.ManaMax == 0 ? (ushort)1 : m.ManaMax)); + + m.ManaMax = p.ReadUInt16(); + m.Mana = p.ReadUInt16(); + + + } + } + + private static void MobileStatInfo(PacketReader pvSrc, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile(pvSrc.ReadUInt32()); + if (m == null) + return; + PlayerData p = World.Player; + + m.HitsMax = pvSrc.ReadUInt16(); + m.Hits = pvSrc.ReadUInt16(); + + m.ManaMax = pvSrc.ReadUInt16(); + m.Mana = pvSrc.ReadUInt16(); + + m.StamMax = pvSrc.ReadUInt16(); + m.Stam = pvSrc.ReadUInt16(); + + + } + + public static bool UseNewStatus = false; + + private static void NewMobileStatus(PacketReader p, PacketHandlerEventArgs args) + { + Mobile m = World.FindMobile((Serial)p.ReadUInt32()); + + if (m == null) + return; + + UseNewStatus = true; + + // 00 01 + p.ReadUInt16(); + + // 00 01 Poison + // 00 02 Yellow Health Bar + + ushort id = p.ReadUInt16(); + + // 00 Off + // 01 On + // For Poison: Poison Level + 1 + + byte flag = p.ReadByte(); + + if (id == 1) + { + bool wasPoisoned = m.Poisoned; + m.Poisoned = (flag != 0); + + + } + } + + //private static void Damage(PacketReader p, PacketHandlerEventArgs args) + //{ + // if (Config.GetBool("TrackDps")) + // { + // uint serial = p.ReadUInt32(); + // ushort damage = p.ReadUInt16(); + + // if (serial != World.Player.Serial) + // return; + + // World.Player.AddDamage(damage); + // } + + //} + + private static void MobileStatus(PacketReader p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + Mobile m = World.FindMobile(serial); + if (m == null) + World.AddMobile(m = new Mobile(serial)); + + m.Name = p.ReadString(30); + + m.Hits = p.ReadUInt16(); + m.HitsMax = p.ReadUInt16(); + + //p.ReadBoolean();//CanBeRenamed + if (p.ReadBoolean()) + m.CanRename = true; + + byte type = p.ReadByte(); + + if (m == World.Player && type != 0x00) + { + PlayerData player = (PlayerData)m; + + player.Female = p.ReadBoolean(); + + int oStr = player.Str, oDex = player.Dex, oInt = player.Int; + + player.Str = p.ReadUInt16(); + player.Dex = p.ReadUInt16(); + player.Int = p.ReadUInt16(); + + + + player.Stam = p.ReadUInt16(); + player.StamMax = p.ReadUInt16(); + player.Mana = p.ReadUInt16(); + player.ManaMax = p.ReadUInt16(); + + player.Gold = p.ReadUInt32(); + player.AR = p.ReadUInt16(); // ar / physical resist + player.Weight = p.ReadUInt16(); + + if (type >= 0x03) + { + if (type > 0x04) + { + player.MaxWeight = p.ReadUInt16(); + + p.ReadByte(); // race? + } + + player.StatCap = p.ReadUInt16(); + + player.Followers = p.ReadByte(); + player.FollowersMax = p.ReadByte(); + + if (type > 0x03) + { + player.FireResistance = p.ReadInt16(); + player.ColdResistance = p.ReadInt16(); + player.PoisonResistance = p.ReadInt16(); + player.EnergyResistance = p.ReadInt16(); + + player.Luck = p.ReadInt16(); + + player.DamageMin = p.ReadUInt16(); + player.DamageMax = p.ReadUInt16(); + + player.Tithe = p.ReadInt32(); + } + } + + + + } + } + + private static void MobileUpdate(Packet p, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + Serial serial = p.ReadUInt32(); + Mobile m = World.FindMobile(serial); + if (m == null) + World.AddMobile(m = new Mobile(serial)); + + bool wasHidden = !m.Visible; + + m.Body = (ushort)(p.ReadUInt16() + p.ReadSByte()); + m.Hue = p.ReadUInt16(); + int ltHue = Config.GetInt("LTHilight"); + if (ltHue != 0 && Targeting.IsLastTarget(m)) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((ushort)(ltHue | 0x8000)); + } + + bool wasPoisoned = m.Poisoned; + m.ProcessPacketFlags(p.ReadByte()); + + ushort x = p.ReadUInt16(); + ushort y = p.ReadUInt16(); + p.ReadUInt16(); //always 0? + m.Direction = (Direction)p.ReadByte(); + m.Position = new Point3D(x, y, p.ReadSByte()); + + + + Item.UpdateContainers(); + } + + private static void MobileIncoming(Packet p, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + Serial serial = p.ReadUInt32(); + ushort body = p.ReadUInt16(); + Point3D position = new Point3D(p.ReadUInt16(), p.ReadUInt16(), p.ReadSByte()); + + if (World.Player.Position != Point3D.Zero && !Utility.InRange(World.Player.Position, position, World.Player.VisRange)) + return; + + Mobile m = World.FindMobile(serial); + if (m == null) + World.AddMobile(m = new Mobile(serial)); + + bool wasHidden = !m.Visible; + + if (m != World.Player && Config.GetBool("ShowMobNames")) + ClientCommunication.SendToServer(new SingleClick(m)); + if (Config.GetBool("LastTargTextFlags")) + Targeting.CheckTextFlags(m); + + int ltHue = Config.GetInt("LTHilight"); + bool isLT; + if (ltHue != 0) + isLT = Targeting.IsLastTarget(m); + else + isLT = false; + + m.Body = body; + if (m != World.Player) + m.Position = position; + m.Direction = (Direction)p.ReadByte(); + m.Hue = p.ReadUInt16(); + if (isLT) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((short)(ltHue | 0x8000)); + } + + bool wasPoisoned = m.Poisoned; + m.ProcessPacketFlags(p.ReadByte()); + byte oldNoto = m.Notoriety; + m.Notoriety = p.ReadByte(); + + + + while (true) + { + serial = p.ReadUInt32(); + if (!serial.IsItem) + break; + + Item item = World.FindItem(serial); + bool isNew = false; + if (item == null) + { + isNew = true; + World.AddItem(item = new Item(serial)); + } + + if (!DragDropManager.EndHolding(serial)) + continue; + + item.Container = m; + + ushort id = p.ReadUInt16(); + + if (Engine.UseNewMobileIncoming) + item.ItemID = (ushort)(id & 0xFFFF); + else if (Engine.UsePostSAChanges) + item.ItemID = (ushort)(id & 0x7FFF); + else + item.ItemID = (ushort)(id & 0x3FFF); + + item.Layer = (Layer)p.ReadByte(); + + if (Engine.UseNewMobileIncoming) + { + item.Hue = p.ReadUInt16(); + if (isLT) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((short)(ltHue & 0x3FFF)); + } + } + else + { + if ((id & 0x8000) != 0) + { + item.Hue = p.ReadUInt16(); + if (isLT) + { + p.Seek(-2, SeekOrigin.Current); + p.Write((short)(ltHue & 0x3FFF)); + } + } + else + { + item.Hue = 0; + if (isLT) + ClientCommunication.SendToClient(new EquipmentItem(item, (ushort)(ltHue & 0x3FFF), m.Serial)); + } + } + + if (item.Layer == Layer.Backpack && isNew && Config.GetBool("AutoSearch") && m == World.Player && m != null) + { + m_IgnoreGumps.Add(item); + PlayerData.DoubleClick(item); + } + } + + Item.UpdateContainers(); + } + + private static void RemoveObject(PacketReader p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + + if (serial.IsMobile) + { + Mobile m = World.FindMobile(serial); + if (m != null && m != World.Player) + m.Remove(); + } + else if (serial.IsItem) + { + Item i = World.FindItem(serial); + if (i != null) + { + if (DragDropManager.Holding == i) + { + i.Container = null; + } + else + { + i.RemoveRequest(); + } + } + } + } + + private static void ServerChange(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player != null) + World.Player.Position = new Point3D(p.ReadUInt16(), p.ReadUInt16(), p.ReadInt16()); + } + + private static void WorldItem(PacketReader p, PacketHandlerEventArgs args) + { + Item item; + uint serial = p.ReadUInt32(); + item = World.FindItem(serial & 0x7FFFFFFF); + bool isNew = false; + if (item == null) + { + World.AddItem(item = new Item(serial & 0x7FFFFFFF)); + isNew = true; + } + else + { + item.CancelRemove(); + } + + if (!DragDropManager.EndHolding(serial)) + return; + + item.Container = null; + + ushort itemID = p.ReadUInt16(); + item.ItemID = (ushort)(itemID & 0x7FFF); + + if ((serial & 0x80000000) != 0) + item.Amount = p.ReadUInt16(); + else + item.Amount = 1; + + if ((itemID & 0x8000) != 0) + item.ItemID = (ushort)(item.ItemID + p.ReadSByte()); + + ushort x = p.ReadUInt16(); + ushort y = p.ReadUInt16(); + + if ((x & 0x8000) != 0) + item.Direction = p.ReadByte(); + else + item.Direction = 0; + + short z = p.ReadSByte(); + + item.Position = new Point3D(x & 0x7FFF, y & 0x3FFF, z); + + if ((y & 0x8000) != 0) + item.Hue = p.ReadUInt16(); + else + item.Hue = 0; + + byte flags = 0; + if ((y & 0x4000) != 0) + flags = p.ReadByte(); + + item.ProcessPacketFlags(flags); + + if (isNew && World.Player != null) + { + if (item.ItemID == 0x2006)// corpse itemid = 0x2006 + { + if (Config.GetBool("ShowCorpseNames")) + ClientCommunication.SendToServer(new SingleClick(item)); + + if (Config.GetBool("AutoOpenCorpses") && Utility.InRange(item.Position, World.Player.Position, Config.GetInt("CorpseRange")) && World.Player != null && World.Player.Visible) + { + if (Config.GetBool("BlockOpenCorpsesTwice")) + { + bool blockOpen = false; + foreach (uint openedCorse in World.Player.OpenedCorpses) + { + if (openedCorse == serial) + { + blockOpen = true; + break; + } + } + + if (World.Player.OpenedCorpses.Count > 2000) + { + World.Player.OpenedCorpses.RemoveRange(0, 500); + } + + if (!blockOpen) + { + PlayerData.DoubleClick(item); + } + + if (!World.Player.OpenedCorpses.Contains(serial)) + { + World.Player.OpenedCorpses.Add(serial); + } + + + } + else + { + PlayerData.DoubleClick(item); + } + } + } + else if (item.IsMulti) + { + } + else + { + + } + } + + Item.UpdateContainers(); + } + + private static void SAWorldItem(PacketReader p, PacketHandlerEventArgs args) + { + /* + New World Item Packet + PacketID: 0xF3 + PacketLen: 24 + Format: + + BYTE - 0xF3 packetId + WORD - 0x01 + BYTE - ArtDataID: 0x00 if the item uses art from TileData table, 0x02 if the item uses art from MultiData table) + DWORD - item Serial + WORD - item ID + BYTE - item direction (same as old) + WORD - amount + WORD - amount + WORD - X + WORD - Y + SBYTE - Z + BYTE - item light + WORD - item Hue + BYTE - item flags (same as old packet) + */ + + // Post-7.0.9.0 + /* + New World Item Packet + PacketID: 0xF3 + PacketLen: 26 + Format: + + BYTE - 0xF3 packetId + WORD - 0x01 + BYTE - ArtDataID: 0x00 if the item uses art from TileData table, 0x02 if the item uses art from MultiData table) + DWORD - item Serial + WORD - item ID + BYTE - item direction (same as old) + WORD - amount + WORD - amount + WORD - X + WORD - Y + SBYTE - Z + BYTE - item light + WORD - item Hue + BYTE - item flags (same as old packet) + WORD ??? + */ + + ushort _unk1 = p.ReadUInt16(); + + byte _artDataID = p.ReadByte(); + + Item item; + uint serial = p.ReadUInt32(); + item = World.FindItem(serial); + bool isNew = false; + if (item == null) + { + World.AddItem(item = new Item(serial)); + isNew = true; + } + else + { + item.CancelRemove(); + } + + if (!DragDropManager.EndHolding(serial)) + return; + + item.Container = null; + + ushort itemID = p.ReadUInt16(); + item.ItemID = (ushort)(_artDataID == 0x02 ? itemID | 0x4000 : itemID); + + item.Direction = p.ReadByte(); + + ushort _amount = p.ReadUInt16(); + item.Amount = _amount = p.ReadUInt16(); + + ushort x = p.ReadUInt16(); + ushort y = p.ReadUInt16(); + short z = p.ReadSByte(); + + item.Position = new Point3D(x, y, z); + + byte _light = p.ReadByte(); + + item.Hue = p.ReadUInt16(); + + byte flags = p.ReadByte(); + + item.ProcessPacketFlags(flags); + + if (Engine.UsePostHSChanges) + { + p.ReadUInt16(); + } + + if (isNew && World.Player != null) + { + if (item.ItemID == 0x2006)// corpse itemid = 0x2006 + { + if (Config.GetBool("ShowCorpseNames")) + ClientCommunication.SendToServer(new SingleClick(item)); + if (Config.GetBool("AutoOpenCorpses") && Utility.InRange(item.Position, World.Player.Position, Config.GetInt("CorpseRange")) && World.Player != null && World.Player.Visible) + PlayerData.DoubleClick(item); + } + else if (item.IsMulti) + { + } + else + { + + } + } + + Item.UpdateContainers(); + } + + public static List SysMessages = new List(); + + public static void HandleSpeech(Packet p, PacketHandlerEventArgs args, Serial ser, ushort body, MessageType type, ushort hue, ushort font, string lang, string name, string text) + { + if (World.Player == null) + return; + CEasyUO.EUOInterpreter.AddToJournal( text ); + if (type == MessageType.Spell) + { + Spell s = Spell.Get(text.Trim()); + bool replaced = false; + if (s != null) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(Config.GetString("SpellFormat")); + sb.Replace(@"{power}", s.WordsOfPower); + string spell = Language.GetString(s.Name); + sb.Replace(@"{spell}", spell); + sb.Replace(@"{name}", spell); + sb.Replace(@"{circle}", s.Circle.ToString()); + + string newText = sb.ToString(); + + if (newText != null && newText != "" && newText != text) + { + ClientCommunication.SendToClient(new AsciiMessage(ser, body, MessageType.Spell, s.GetHue(hue), font, name, newText)); + //ClientCommunication.SendToClient( new UnicodeMessage( ser, body, MessageType.Spell, s.GetHue( hue ), font, Language.CliLocName, name, newText ) ); + replaced = true; + args.Block = true; + } + } + + if (!replaced && Config.GetBool("ForceSpellHue")) + { + p.Seek(10, SeekOrigin.Begin); + if (s != null) + p.Write((ushort)s.GetHue(hue)); + else + p.Write((ushort)Config.GetInt("NeutralSpellHue")); + } + } + else if (ser.IsMobile && type == MessageType.Label) + { + Mobile m = World.FindMobile(ser); + if (m != null /*&& ( m.Name == null || m.Name == "" || m.Name == "(Not Seen)" )*/&& m.Name.IndexOf(text) != 5 && m != World.Player && !(text.StartsWith("(") && text.EndsWith(")"))) + m.Name = text; + } + /*else if ( Spell.Get( text.Trim() ) != null ) + { // send fake spells to bottom left + p.Seek( 3, SeekOrigin.Begin ); + p.Write( (uint)0xFFFFFFFF ); + }*/ + else + { + if (ser == Serial.MinusOne && name == "System") + { + if (Config.GetBool("FilterSnoopMsg") && text.IndexOf(World.Player.Name) == -1 && text.StartsWith("You notice") && text.IndexOf("attempting to peek into") != -1 && text.IndexOf("belongings") != -1) + { + args.Block = true; + return; + } + + if (text.StartsWith("You've committed a criminal act") || text.StartsWith("You are now a criminal")) + { + } + + // Overhead message override + if (Config.GetBool("ShowOverheadMessages") && OverheadMessages.OverheadMessageList.Count > 0) + { + string overheadFormat = Config.GetString("OverheadFormat"); + + foreach (OverheadMessages.OverheadMessage message in OverheadMessages.OverheadMessageList) + { + if (text.IndexOf(message.SearchMessage, StringComparison.OrdinalIgnoreCase) != -1) + { + World.Player.OverheadMessage(overheadFormat.Replace("{msg}", message.MessageOverhead)); + break; + } + } + } + } + + if (Config.GetBool("ShowContainerLabels") && ser.IsItem) + { + Item item = World.FindItem(ser); + + if (item == null || !item.IsContainer) + return; + + foreach (ContainerLabels.ContainerLabel label in ContainerLabels.ContainerLabelList) + { + // Check if its the serial match and if the text matches the name (since we override that for the label) + if (Serial.Parse(label.Id) == ser && (item.DisplayName.Equals(text) || label.Alias.Equals(text, StringComparison.InvariantCultureIgnoreCase))) + { + string labelDisplay = $"{Config.GetString("ContainerLabelFormat").Replace("{label}", label.Label).Replace("{type}", text)}"; + + //ContainerLabelStyle + if (Config.GetInt("ContainerLabelStyle") == 0) + { + ClientCommunication.SendToClient(new AsciiMessage(ser, item.ItemID.Value, MessageType.Label, label.Hue, 3, Language.CliLocName, labelDisplay)); + + } + else + { + ClientCommunication.SendToClient(new UnicodeMessage(ser, item.ItemID.Value, MessageType.Label, label.Hue, 3, Language.CliLocName, "", labelDisplay)); + } + + // block the actual message from coming through since we have it in the label + args.Block = true; + + ContainerLabels.LastContainerLabelDisplayed = ser; + + break; + } + } + } + + if ((type == MessageType.Emote || type == MessageType.Regular || type == MessageType.Whisper || type == MessageType.Yell) && ser.IsMobile && ser != World.Player.Serial) + { + + + if (Config.GetBool("ForceSpeechHue")) + { + p.Seek(10, SeekOrigin.Begin); + p.Write((ushort)Config.GetInt("SpeechHue")); + } + } + + if (!ser.IsValid || ser == World.Player.Serial || ser.IsItem) + { + SysMessages.Add(text); + + if (SysMessages.Count >= 25) + SysMessages.RemoveRange(0, 10); + } + } + + } + + public static void AsciiSpeech(Packet p, PacketHandlerEventArgs args) + { + // 0, 1, 2 + Serial serial = p.ReadUInt32(); // 3, 4, 5, 6 + ushort body = p.ReadUInt16(); // 7, 8 + MessageType type = (MessageType)p.ReadByte(); // 9 + ushort hue = p.ReadUInt16(); // 10, 11 + ushort font = p.ReadUInt16(); + string name = p.ReadStringSafe(30); + string text = p.ReadStringSafe(); + + if (World.Player != null && serial == Serial.Zero && body == 0 && type == MessageType.Regular && hue == 0xFFFF && font == 0xFFFF && name == "SYSTEM") + { + args.Block = true; + + p.Seek(3, SeekOrigin.Begin); + p.WriteAsciiFixed("", (int)p.Length - 3); + } + else + { + HandleSpeech(p, args, serial, body, type, hue, font, "A", name, text); + + + + + } + } + + public static void UnicodeSpeech(Packet p, PacketHandlerEventArgs args) + { + // 0, 1, 2 + Serial serial = p.ReadUInt32(); // 3, 4, 5, 6 + ushort body = p.ReadUInt16(); // 7, 8 + MessageType type = (MessageType)p.ReadByte(); // 9 + ushort hue = p.ReadUInt16(); // 10, 11 + ushort font = p.ReadUInt16(); + string lang = p.ReadStringSafe(4); + string name = p.ReadStringSafe(30); + string text = p.ReadUnicodeStringSafe(); + + HandleSpeech(p, args, serial, body, type, hue, font, lang, name, text); + } + + private static void OnLocalizedMessage(Packet p, PacketHandlerEventArgs args) + { + // 0, 1, 2 + Serial serial = p.ReadUInt32(); // 3, 4, 5, 6 + ushort body = p.ReadUInt16(); // 7, 8 + MessageType type = (MessageType)p.ReadByte(); // 9 + ushort hue = p.ReadUInt16(); // 10, 11 + ushort font = p.ReadUInt16(); + int num = p.ReadInt32(); + string name = p.ReadStringSafe(30); + string ext_str = p.ReadUnicodeStringLESafe(); + + if ((num >= 3002011 && num < 3002011 + 64) || // reg spells + (num >= 1060509 && num < 1060509 + 16) || // necro + (num >= 1060585 && num < 1060585 + 10) || // chiv + (num >= 1060493 && num < 1060493 + 10) || // chiv + (num >= 1060595 && num < 1060595 + 6) || // bush + (num >= 1060610 && num < 1060610 + 8)) // ninj + { + type = MessageType.Spell; + } + + + try + { + string text = Language.ClilocFormat(num, ext_str); + HandleSpeech(p, args, serial, body, type, hue, font, Language.CliLocName.ToUpper(), name, text); + } + catch (Exception e) + { + Engine.LogCrash(new Exception(String.Format("Exception in Ultima.dll cliloc: {0}, {1}", num, ext_str), e)); + } + } + + private static void OnLocalizedMessageAffix(Packet p, PacketHandlerEventArgs phea) + { + // 0, 1, 2 + Serial serial = p.ReadUInt32(); // 3, 4, 5, 6 + ushort body = p.ReadUInt16(); // 7, 8 + MessageType type = (MessageType)p.ReadByte(); // 9 + ushort hue = p.ReadUInt16(); // 10, 11 + ushort font = p.ReadUInt16(); + int num = p.ReadInt32(); + byte affixType = p.ReadByte(); + string name = p.ReadStringSafe(30); + string affix = p.ReadStringSafe(); + string args = p.ReadUnicodeStringSafe(); + + if ((num >= 3002011 && num < 3002011 + 64) || // reg spells + (num >= 1060509 && num < 1060509 + 16) || // necro + (num >= 1060585 && num < 1060585 + 10) || // chiv + (num >= 1060493 && num < 1060493 + 10) || // chiv + (num >= 1060595 && num < 1060595 + 6) || // bush + (num >= 1060610 && num < 1060610 + 8) // ninj + ) + { + type = MessageType.Spell; + } + + string text; + if ((affixType & 1) != 0) // prepend + text = String.Format("{0}{1}", affix, Language.ClilocFormat(num, args)); + else // 0 == append, 2 = system + text = String.Format("{0}{1}", Language.ClilocFormat(num, args), affix); + HandleSpeech(p, phea, serial, body, type, hue, font, Language.CliLocName.ToUpper(), name, text); + } + + private static void SendGump(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + World.Player.CurrentGumpS = p.ReadUInt32(); + World.Player.CurrentGumpI = p.ReadUInt32(); + World.Player.HasGump = true; + World.Player.LastGumpOpenedAt = DateTime.UtcNow; + + //byte[] data = p.CopyBytes( 11, p.Length - 11 ); + + //if (Macros.MacroManager.AcceptActions && MacroManager.Action(new WaitForGumpAction(World.Player.CurrentGumpI))) + // args.Block = true; + } + + private static void ClientGumpResponse(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + Serial ser = p.ReadUInt32(); + uint tid = p.ReadUInt32(); + int bid = p.ReadInt32(); + + World.Player.HasGump = false; + + int sc = p.ReadInt32(); + if (sc < 0 || sc > 2000) + return; + int[] switches = new int[sc]; + for (int i = 0; i < sc; i++) + switches[i] = p.ReadInt32(); + + int ec = p.ReadInt32(); + if (ec < 0 || ec > 2000) + return; + GumpTextEntry[] entries = new GumpTextEntry[ec]; + for (int i = 0; i < ec; i++) + { + ushort id = p.ReadUInt16(); + ushort len = p.ReadUInt16(); + if (len >= 240) + return; + string text = p.ReadUnicodeStringSafe(len); + entries[i] = new GumpTextEntry(id, text); + } + + // if (Macros.MacroManager.AcceptActions) + // MacroManager.Action(new GumpResponseAction(bid, switches, entries)); + if(bid != 0) + World.Player.LastGumpResponseAction = new GumpResponseAction(bid, switches, entries); + } + + private static void ChangeSeason(PacketReader p, PacketHandlerEventArgs args) + { + + } + + private static void ExtendedPacket(PacketReader p, PacketHandlerEventArgs args) + { + ushort type = p.ReadUInt16(); + + switch (type) + { + case 0x04: // close gump + { + // int serial, int tid + if (World.Player != null) + World.Player.HasGump = false; + break; + } + case 0x06: // party messages + { + OnPartyMessage(p, args); + break; + } + case 0x08: // map change + { + if (World.Player != null) + World.Player.Map = p.ReadByte(); + break; + } + case 0x14: // context menu + { + p.ReadInt16(); // 0x01 + UOEntity ent = null; + Serial ser = p.ReadUInt32(); + if (ser.IsMobile) + ent = World.FindMobile(ser); + else if (ser.IsItem) + ent = World.FindItem(ser); + + if (ent != null) + { + byte count = p.ReadByte(); + + try + { + ent.ContextMenu.Clear(); + + for (int i = 0; i < count; i++) + { + ushort idx = p.ReadUInt16(); + ushort num = p.ReadUInt16(); + ushort flags = p.ReadUInt16(); + ushort color = 0; + + if ((flags & 0x02) != 0) + color = p.ReadUInt16(); + + ent.ContextMenu.Add(idx, num); + } + } + catch + { + } + } + break; + } + case 0x18: // map patches + { + if (World.Player != null) + { + int count = p.ReadInt32() * 2; + try + { + World.Player.MapPatches = new int[count]; + for (int i = 0; i < count; i++) + World.Player.MapPatches[i] = p.ReadInt32(); + } + catch + { + } + } + break; + } + case 0x19: // stat locks + { + if (p.ReadByte() == 0x02) + { + Mobile m = World.FindMobile(p.ReadUInt32()); + if (World.Player == m && m != null) + { + p.ReadByte();// 0? + + byte locks = p.ReadByte(); + + World.Player.StrLock = (LockType)((locks >> 4) & 3); + World.Player.DexLock = (LockType)((locks >> 2) & 3); + World.Player.IntLock = (LockType)(locks & 3); + } + } + break; + } + case 0x1D: // Custom House "General Info" + { + Item i = World.FindItem(p.ReadUInt32()); + if (i != null) + i.HouseRevision = p.ReadInt32(); + break; + } + } + } + + public static int SpecialPartySent = 0; + public static int SpecialPartyReceived = 0; + + private static void RunUOProtocolExtention(PacketReader p, PacketHandlerEventArgs args) + { + args.Block = true; + + switch (p.ReadByte()) + { + case 1: // Custom Party information + { + Serial serial; + + PacketHandlers.SpecialPartyReceived++; + + while ((serial = p.ReadUInt32()) > 0) + { + Mobile mobile = World.FindMobile(serial); + + short x = p.ReadInt16(); + short y = p.ReadInt16(); + byte map = p.ReadByte(); + + if (mobile == null) + { + World.AddMobile(mobile = new Mobile(serial)); + mobile.Visible = false; + } + + if (mobile.Name == null || mobile.Name.Length <= 0) + mobile.Name = "(Not Seen)"; + + if (!m_Party.Contains(serial)) + m_Party.Add(serial); + + if (map == World.Player.Map) + mobile.Position = new Point3D(x, y, mobile.Position.Z); + else + mobile.Position = Point3D.Zero; + } + + + + break; + } + case 0xFE: // Begin Handshake/Features Negotiation + { + ulong features = p.ReadRawUInt64(); + + + ClientCommunication.SendToServer(new RazorNegotiateResponse()); + + break; + } + } + } + + private static List m_Party = new List(); + public static List Party { get { return m_Party; } } + private static Timer m_PartyDeclineTimer = null; + public static Serial PartyLeader = Serial.Zero; + + private static void OnPartyMessage(PacketReader p, PacketHandlerEventArgs args) + { + switch (p.ReadByte()) + { + case 0x01: // List + { + m_Party.Clear(); + + int count = p.ReadByte(); + for (int i = 0; i < count; i++) + { + Serial s = p.ReadUInt32(); + if (World.Player == null || s != World.Player.Serial) + m_Party.Add(s); + } + + break; + } + case 0x02: // Remove Member/Re-list + { + m_Party.Clear(); + int count = p.ReadByte(); + Serial remSerial = p.ReadUInt32(); // the serial of who was removed + + if (World.Player != null) + { + Mobile rem = World.FindMobile(remSerial); + if (rem != null && !Utility.InRange(World.Player.Position, rem.Position, World.Player.VisRange)) + rem.Remove(); + } + + for (int i = 0; i < count; i++) + { + Serial s = p.ReadUInt32(); + if (World.Player == null || s != World.Player.Serial) + m_Party.Add(s); + } + + break; + } + case 0x03: // text message + + case 0x04: // 3 = private, 4 = public + { + Serial s = p.ReadUInt32(); + string text = p.ReadUnicodeStringSafe(); + + + var data = new List(); + + if (text.StartsWith("New marker: ")) + { + string name = World.FindMobile(s).Name; + string trimmed = text.Substring(12); + string[] message = trimmed.Split(','); + data.Add(message); + + foreach (string[] line in data) + { + float x = float.Parse(line[0]); + float y = float.Parse(line[1]); + string displayText = line[2]; + string extraText = line[3]; + + string markerOwner = name; + } + } + break; + } + case 0x07: // party invite + { + //Serial leader = p.ReadUInt32(); + PartyLeader = p.ReadUInt32(); + + + + break; + } + } + +; + } + + private static void PartyAutoDecline() + { + PartyLeader = Serial.Zero; + } + + private static void PingResponse(PacketReader p, PacketHandlerEventArgs args) + { + + } + + private static void ClientEncodedPacket(PacketReader p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + ushort packetID = p.ReadUInt16(); + switch (packetID) + { + case 0x19: // set ability + { + int ability = 0; + if (p.ReadByte() == 0) + ability = p.ReadInt32(); + + // if (ability >= 0 && ability < (int)AOSAbility.Invalid && Macros.MacroManager.AcceptActions) + // MacroManager.Action(new SetAbilityAction((AOSAbility)ability)); + break; + } + } + } + + private static void GameLogin(Packet p, PacketHandlerEventArgs args) + { + int authID = p.ReadInt32(); + + World.AccountName = p.ReadString(30); + + // TODO: Do we need to store account name? + } + + private static void MenuResponse(PacketReader pvSrc, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + uint serial = pvSrc.ReadUInt32(); + ushort menuID = pvSrc.ReadUInt16(); + ushort index = pvSrc.ReadUInt16(); + ushort itemID = pvSrc.ReadUInt16(); + ushort hue = pvSrc.ReadUInt16(); + + World.Player.HasMenu = false; + // if (MacroManager.AcceptActions) + // MacroManager.Action(new MenuResponseAction(index, itemID, hue)); + } + + private static void SendMenu(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player == null) + return; + + World.Player.CurrentMenuS = p.ReadUInt32(); + World.Player.CurrentMenuI = p.ReadUInt16(); + World.Player.HasMenu = true; + // if (MacroManager.AcceptActions && MacroManager.Action(new WaitForMenuAction(World.Player.CurrentMenuI))) + // args.Block = true; + } + + private static void HueResponse(PacketReader p, PacketHandlerEventArgs args) + { + Serial serial = p.ReadUInt32(); + ushort iid = p.ReadUInt16(); + ushort hue = p.ReadUInt16(); + + if (serial == Serial.MinusOne) + { + + args.Block = true; + } + } + + private static void ServerAddress(Packet p, PacketHandlerEventArgs args) + { + + } + + private static void Features(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player != null) + World.Player.Features = p.ReadUInt16(); + } + + private static void PersonalLight(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player != null && !args.Block) + { + p.ReadUInt32(); // serial + + World.Player.LocalLightLevel = p.ReadSByte(); + + if (EnforceLightLevels(World.Player.LocalLightLevel)) + args.Block = true; + } + } + + private static void GlobalLight(PacketReader p, PacketHandlerEventArgs args) + { + if (World.Player != null && !args.Block) + { + World.Player.GlobalLightLevel = p.ReadByte(); + + if (EnforceLightLevels(World.Player.GlobalLightLevel)) + args.Block = true; + } + } + + private static bool EnforceLightLevels(int lightLevel) + { + if (Config.GetBool("MinMaxLightLevelEnabled")) + { + // 0 bright, 30 is dark + + if (lightLevel < Config.GetInt("MaxLightLevel")) + { + lightLevel = Convert.ToByte(Config.GetInt("MaxLightLevel")); // light level is too light + } + else if (lightLevel > Config.GetInt("MinLightLevel")) // light level is too dark + { + lightLevel = Convert.ToByte(Config.GetInt("MinLightLevel")); + } + else // No need to block or do anything special + { + return false; + } + + World.Player.LocalLightLevel = 0; + World.Player.GlobalLightLevel = (byte) lightLevel; + + ClientCommunication.SendToClient(new GlobalLightLevel(lightLevel)); + ClientCommunication.SendToClient(new PersonalLightLevel(World.Player)); + + return true; + } + + return false; + } + + private static void ServerSetWarMode(PacketReader p, PacketHandlerEventArgs args) + { + World.Player.Warmode = p.ReadBoolean(); + } + + private static void CustomHouseInfo(PacketReader p, PacketHandlerEventArgs args) + { + p.ReadByte(); // compression + p.ReadByte(); // Unknown + + Item i = World.FindItem(p.ReadUInt32()); + if (i != null) + { + i.HouseRevision = p.ReadInt32(); + i.HousePacket = p.CopyBytes(0, p.Length); + } + } + + /* + Packet Build + 1. BYTE[1] Cmd + 2. BYTE[2] len + 3. BYTE[4] Player Serial + 4. BYTE[4] Gump ID + 5. BYTE[4] x + 6. BYTE[4] y + 7. BYTE[4] Compressed Gump Layout Length (CLen) + 8. BYTE[4] Decompressed Gump Layout Length (DLen) + 9. BYTE[CLen-4] Gump Data, zlib compressed + 10. BYTE[4] Number of text lines + 11. BYTE[4] Compressed Text Line Length (CTxtLen) + 12. BYTE[4] Decompressed Text Line Length (DTxtLen) + 13. BYTE[CTxtLen-4] Gump's Compressed Text data, zlib compressed + */ + private static void CompressedGump(PacketReader p, PacketHandlerEventArgs args) + { + World.Player.HasGump = true; + if (World.Player == null) + return; + + World.Player.CurrentGumpS = p.ReadUInt32(); + World.Player.CurrentGumpI = p.ReadUInt32(); + World.Player.LastGumpX = p.ReadUInt32(); + World.Player.LastGumpY = p.ReadUInt32(); + World.Player.LastGumpWidth = 0; + World.Player.LastGumpHeight = 0; + + World.Player.LastGumpOpenedAt = DateTime.UtcNow; + + // if (Macros.MacroManager.AcceptActions && MacroManager.Action(new WaitForGumpAction(World.Player.CurrentGumpI))) + // args.Block = true; + + List gumpStrings = new List(); + + try + { + + string layout = p.GetCompressedReader().ReadString(); + + int numStrings = p.ReadInt32(); + if (numStrings < 0 || numStrings > 256) + numStrings = 0; + + // Split on one or more non-digit characters. + World.Player.CurrentGumpStrings.Clear(); + + string[] numbers = Regex.Split(layout, @"\D+"); + + foreach (string value in numbers) + { + if (!string.IsNullOrEmpty(value)) + { + int i = int.Parse(value); + if ((i >= 500000 && i <= 503405) || (i >= 1000000 && i <= 1155584) || (i >= 3000000 && i <= 3011032)) + gumpStrings.Add(Language.GetString(i)); + } + } + + PacketReader pComp = p.GetCompressedReader(); + int len = 0; + int x1 = 0; + string[] stringlistparse = new string[numStrings]; + + while (!pComp.AtEnd && (len = pComp.ReadInt16()) > 0) + { + string tempString = pComp.ReadUnicodeString(len); + stringlistparse[x1] = tempString; + x1++; + } + + if (TryParseGump(layout, out string[] gumpPieces)) + { + gumpStrings.AddRange(ParseGumpString(gumpPieces, stringlistparse)); + } + + World.Player.CurrentGumpStrings.AddRange(gumpStrings); + World.Player.CurrentGumpRawData = layout; // Get raw data of current gump + } + catch { } + } + + private static bool TryParseGump(string gumpData, out string[] pieces) + { + List i = new List(); + int dataIndex = 0; + while (dataIndex < gumpData.Length) + { + if (gumpData.Substring(dataIndex) == "\0") + { + break; + } + else + { + int begin = gumpData.IndexOf("{", dataIndex); + int end = gumpData.IndexOf("}", dataIndex + 1); + if ((begin != -1) && (end != -1)) + { + string sub = gumpData.Substring(begin + 1, end - begin - 1).Trim(); + i.Add(sub); + dataIndex = end; + } + else + { + break; + } + } + } + + pieces = i.ToArray(); + return (pieces.Length > 0); + } + + private static List ParseGumpString(string[] gumpPieces, string[] gumpLines) + { + List gumpText = new List(); + for (int i = 0; i < gumpPieces.Length; i++) + { + string[] gumpParams = Regex.Split(gumpPieces[i], @"\s+"); + switch (gumpParams[0].ToLower()) + { + + case "croppedtext": + gumpText.Add(gumpLines[int.Parse(gumpParams[6])]); + // CroppedText [x] [y] [width] [height] [color] [text-id] + // Adds a text field to the gump. gump is similar to the text command, but the text is cropped to the defined area. + //gump.AddControl(new CroppedText(gump, gumpParams, gumpLines), currentGUMPPage); + //(gump.LastControl as CroppedText).Hue = 1; + break; + + case "htmlgump": + gumpText.Add(gumpLines[int.Parse(gumpParams[5])]); + // HtmlGump [x] [y] [width] [height] [text-id] [background] [scrollbar] + // Defines a text-area where Html-commands are allowed. + // [background] and [scrollbar] can be 0 or 1 and define whether the background is transparent and a scrollbar is displayed. + // gump.AddControl(new HtmlGumpling(gump, gumpParams, gumpLines), currentGUMPPage); + break; + + case "text": + gumpText.Add(gumpLines[int.Parse(gumpParams[4])]); + // Text [x] [y] [color] [text-id] + // Defines the position and color of a text (data) entry. + //gump.AddControl(new TextLabel(gump, gumpParams, gumpLines), currentGUMPPage); + break; + case "resizepic": + World.Player.LastGumpWidth = Math.Max( int.Parse( gumpParams[4] ), World.Player.LastGumpWidth ); + World.Player.LastGumpHeight = Math.Max( int.Parse( gumpParams[5] ), World.Player.LastGumpHeight ); + break; + } + } + + return gumpText; + } + + private static void ResurrectionGump(PacketReader p, PacketHandlerEventArgs args) + { + + } + + private static void BuffDebuff(PacketReader p, PacketHandlerEventArgs args) + { + Serial ser = p.ReadUInt32(); + ushort icon = p.ReadUInt16(); + ushort action = p.ReadUInt16(); + + if (Enum.IsDefined(typeof(BuffIcon), icon)) + { + BuffIcon buff = (BuffIcon)icon; + + string format = Config.GetString("BuffDebuffFormat"); + if (string.IsNullOrEmpty(format)) + { + format = "[{action}{name}]"; + } + + switch (action) + { + case 0x01: // show + + p.ReadUInt32(); //0x000 + p.ReadUInt16(); //icon # again..? + p.ReadUInt16(); //0x1 = show + p.ReadUInt32(); //0x000 + ushort duration = p.ReadUInt16(); + p.ReadUInt16(); //0x0000 + p.ReadByte(); //0x0 + + BuffsDebuffs buffInfo = new BuffsDebuffs + { + IconNumber = icon, + BuffIcon = (BuffIcon)icon, + ClilocMessage1 = Language.GetCliloc((int)p.ReadUInt32()), + ClilocMessage2 = Language.GetCliloc((int)p.ReadUInt32()), + Duration = duration, + Timestamp = DateTime.UtcNow + }; + + if (World.Player != null && World.Player.BuffsDebuffs.All(b => b.BuffIcon != buff)) + { + World.Player.BuffsDebuffs.Add(buffInfo); + + if (Config.GetBool("ShowBuffDebuffOverhead")) + { + World.Player.OverheadMessage(88, format.Replace("{action}", "+").Replace("{name}", buffInfo.ClilocMessage1)); + } + } + + break; + + case 0x0: // remove + if (World.Player != null)// && World.Player.BuffsDebuffs.Any(b => b.BuffIcon == buff)) + { + if (Config.GetBool("ShowBuffDebuffOverhead")) + { + string buffRemoveInfo = World.Player.BuffsDebuffs.Where(b => b.BuffIcon == buff).Select(x => x.ClilocMessage1).FirstOrDefault(); + World.Player.OverheadMessage(338, format.Replace("{action}", "-").Replace("{name}", buffRemoveInfo)); + } + + World.Player.BuffsDebuffs.RemoveAll(b => b.BuffIcon == buff); + } + + break; + } + + } + + if (World.Player != null && World.Player.BuffsDebuffs.Count > 0) + { + BuffsTimer.Start(); + } + else + { + BuffsTimer.Stop(); + } + } + + private static void AttackRequest(Packet p, PacketHandlerEventArgs args) + { + + } + + private static void TradeRequest(PacketReader p, PacketHandlerEventArgs args) + { + + } + } +} \ No newline at end of file diff --git a/Network/Packet.cs b/Network/Packet.cs new file mode 100644 index 0000000..e59c676 --- /dev/null +++ b/Network/Packet.cs @@ -0,0 +1,2428 @@ +using System; + +using System.IO; + +using System.Text; + +using System.Collections; + + + +namespace Assistant + +{ + + public enum PacketPath + + { + + ClientToServer, + + RazorToServer, + + ServerToClient, + + RazorToClient, + + + + PacketVideo + + } + + + + public class Packet + + { + + private static bool m_Logging = false; + + public static bool Logging + + { + + get + + { + + return m_Logging; + + } + + set + + { + + if ( value != m_Logging ) + + { + + m_Logging = value; + + if ( m_Logging ) + + BeginLog(); + + } + + } + + } + + + + public static string PacketsLogFile + + { + + get + + { + + return "";// return Path.Combine( Config.GetInstallDirectory(), "Razor_Packets.log" ); + + } + + } + + + + private static void BeginLog() + + { + + using ( StreamWriter sw = new StreamWriter( PacketsLogFile, true ) ) + + { + + sw.AutoFlush = true; + + sw.WriteLine(); + + sw.WriteLine(); + + sw.WriteLine(); + + sw.WriteLine( ">>>>>>>>>> Logging started {0} <<<<<<<<<<", DateTime.UtcNow ); + + sw.WriteLine(); + + sw.WriteLine(); + + } + + } + + + + private static byte[] m_Buffer = new byte[4]; // Internal format buffer. + + private MemoryStream m_Stream; + + private bool m_DynSize; + + private byte m_PacketID; + + + + public Packet() + + { + + m_Stream = new MemoryStream(); + + } + + + + public Packet( byte packetID ) + + { + + m_PacketID = packetID; + + m_DynSize = true; + + } + + + + public Packet( byte packetID, int capacity ) + + { + + m_Stream = new MemoryStream( capacity ); + + + + m_PacketID = packetID; + + m_DynSize = false; + + + + m_Stream.WriteByte( packetID ); + + } + + + + public Packet( byte[] data, int len, bool dynLen ) + + { + + m_Stream = new MemoryStream( len ); + + m_PacketID = data[0]; + + m_DynSize = dynLen; + + + + m_Stream.Position = 0; + + m_Stream.Write( data, 0, len ); + + + + MoveToData(); + + } + + + + public void EnsureCapacity( int capacity ) + + { + + m_Stream = new MemoryStream( capacity ); + + Write( (byte)m_PacketID ); + + if ( m_DynSize ) + + Write( (short)0 ); + + } + + + + public byte[] Compile() + + { + + if ( m_DynSize ) + + { + + m_Stream.Seek( 1, SeekOrigin.Begin ); + + Write( (ushort)m_Stream.Length ); + + } + + + + return ToArray(); + + } + + + + public void MoveToData() + + { + + m_Stream.Position = m_DynSize ? 3 : 1; + + } + + + + public void Copy( Packet p ) + + { + + m_Stream = new MemoryStream( (int)p.Length ); + + byte[] data = p.ToArray(); + + m_Stream.Write( data, 0, data.Length ); + + + + m_DynSize = p.m_DynSize; + + m_PacketID = p.m_PacketID; + + + + MoveToData(); + + } + + + + /*public override int GetHashCode() + + { + + long oldPos = m_Stream.Position; + + + + int code = 0; + + + + m_Stream.Position = 0; + + + + while ( m_Stream.Length - m_Stream.Position >= 4 ) + + code ^= ReadInt32(); + + + + code ^= ReadByte() | (ReadByte() << 8) | (ReadByte() << 16) | (ReadByte() << 24); + + + + m_Stream.Position = oldPos; + + + + return code; + + }*/ + + + + public static void Log( string line, params object[] args ) + + { + + Log( String.Format( line, args ) ); + + } + + + + public static void Log( string line ) + + { + + if ( !m_Logging ) + + return; + + + + try + + { + + using ( StreamWriter sw = new StreamWriter( PacketsLogFile, true ) ) + + { + + sw.AutoFlush = true; + + sw.WriteLine( line ); + + sw.WriteLine(); + + } + + } + + catch + + { + + } + + } + + + + public static unsafe void Log( PacketPath path, byte* buff, int len ) + + { + + Log( path, buff, len, false ); + + } + + + + public static unsafe void Log( PacketPath path, byte* buff, int len, bool blocked ) + + { + + if ( !m_Logging ) + + return; + + + + try + + { + + using ( StreamWriter sw = new StreamWriter( PacketsLogFile, true ) ) + + { + + sw.AutoFlush = true; + + + + string pathStr; + + switch ( path ) + + { + + case PacketPath.ClientToServer: + + pathStr = "Client -> Server"; + + break; + + case PacketPath.RazorToServer: + + pathStr = "Razor -> Server"; + + break; + + case PacketPath.ServerToClient: + + pathStr = "Server -> Client"; + + break; + + case PacketPath.RazorToClient: + + pathStr = "Razor -> Client"; + + break; + + case PacketPath.PacketVideo: + + pathStr = "PacketVideo -> Client"; + + break; + + default: + + pathStr = "Unknown -> Unknown"; + + break; + + } + + + + //sw.WriteLine( "{0}: {1}{2}0x{3:X2} (Length: {4})", Engine.MistedDateTime.ToString( "HH:mm:ss.ffff" ), pathStr, blocked ? " [BLOCKED] " : " ", buff[0], len ); + + //if ( buff[0] != 0x80 && buff[0] != 0x91 ) + + // Utility.FormatBuffer( sw, buff, len ); + + //else + + // sw.WriteLine( "[Censored for Security Reasons]" ); + + + + sw.WriteLine(); + + sw.WriteLine(); + + } + + } + + catch + + { + + } + + } + + + + public long Seek( int offset, SeekOrigin origin ) + + { + + return m_Stream.Seek( offset, origin ); + + } + + + + public int ReadInt32() + + { + + if ( m_Stream.Position + 4 > m_Stream.Length ) + + return 0; + + + + return ( ReadByte() << 24 ) + + | ( ReadByte() << 16 ) + + | ( ReadByte() << 8 ) + + | ReadByte(); + + } + + + + public short ReadInt16() + + { + + if ( m_Stream.Position + 2 > m_Stream.Length ) + + return 0; + + return (short)( ( ReadByte() << 8 ) | ReadByte() ); + + } + + + + public byte ReadByte() + + { + + if ( m_Stream.Position + 1 > m_Stream.Length ) + + return 0; + + return (byte)m_Stream.ReadByte(); + + } + + + + public uint ReadUInt32() + + { + + if ( m_Stream.Position + 4 > m_Stream.Length ) + + return 0; + + return (uint)( ( ReadByte() << 24 ) + + | ( ReadByte() << 16 ) + + | ( ReadByte() << 8 ) + + | ReadByte() ); + + } + + + + public ushort ReadUInt16() + + { + + if ( m_Stream.Position + 2 > m_Stream.Length ) + + return 0; + + return (ushort)( ( ReadByte() << 8 ) | ReadByte() ); + + } + + + + public sbyte ReadSByte() + + { + + if ( m_Stream.Position + 1 > m_Stream.Length ) + + return 0; + + return (sbyte)m_Stream.ReadByte(); + + } + + + + public bool ReadBoolean() + + { + + if ( m_Stream.Position + 1 > m_Stream.Length ) + + return false; + + return ( m_Stream.ReadByte() != 0 ); + + } + + + + public string ReadUnicodeStringLE() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position + 1 < m_Stream.Length && ( c = ReadByte() | ( ReadByte() << 8 ) ) != 0 ) + + sb.Append( (char)c ); + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeStringLESafe() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position + 1 < m_Stream.Length && ( c = ReadByte() | ( ReadByte() << 8 ) ) != 0 ) + + { + + if ( IsSafeChar( c ) ) + + sb.Append( (char)c ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeStringSafe() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position + 1 < m_Stream.Length && ( c = ReadUInt16() ) != 0 ) + + { + + if ( IsSafeChar( c ) ) + + sb.Append( (char)c ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeString() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position + 1 < m_Stream.Length && ( c = ReadUInt16() ) != 0 ) + + sb.Append( (char)c ); + + + + return sb.ToString(); + + } + + + + public bool IsSafeChar( int c ) + + { + + return ( c >= 0x20 && c < 0xFFFE ); + + } + + + + public string ReadUTF8StringSafe( int fixedLength ) + + { + + if ( m_Stream.Position >= m_Stream.Length ) + + return String.Empty; + + + + long bound = m_Stream.Position + fixedLength; + + long end = bound; + + + + if ( bound > m_Stream.Length ) + + bound = m_Stream.Length; + + + + int count = 0; + + long index = m_Stream.Position; + + long start = m_Stream.Position; + + + + while ( index < bound && ReadByte() != 0 ) + + ++count; + + + + m_Stream.Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Stream.Position < bound && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + string s = Encoding.UTF8.GetString( buffer ); + + + + bool isSafe = true; + + + + for ( int i = 0; isSafe && i < s.Length; ++i ) + + isSafe = IsSafeChar( (int)s[i] ); + + + + m_Stream.Seek( start + fixedLength, SeekOrigin.Begin ); + + + + if ( isSafe ) + + return s; + + + + StringBuilder sb = new StringBuilder( s.Length ); + + + + for ( int i = 0; i < s.Length; ++i ) + + { + + if ( IsSafeChar( (int)s[i] ) ) + + sb.Append( s[i] ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUTF8StringSafe() + + { + + if ( m_Stream.Position >= m_Stream.Length ) + + return String.Empty; + + + + int count = 0; + + long index = m_Stream.Position; + + long start = index; + + + + while ( index < m_Stream.Length && ReadByte() != 0 ) + + ++count; + + + + m_Stream.Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Stream.Position < m_Stream.Length && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + string s = Encoding.UTF8.GetString( buffer ); + + + + bool isSafe = true; + + + + for ( int i = 0; isSafe && i < s.Length; ++i ) + + isSafe = IsSafeChar( (int)s[i] ); + + + + if ( isSafe ) + + return s; + + + + StringBuilder sb = new StringBuilder( s.Length ); + + + + for ( int i = 0; i < s.Length; ++i ) + + { + + if ( IsSafeChar( (int)s[i] ) ) + + sb.Append( s[i] ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUTF8String() + + { + + if ( m_Stream.Position >= m_Stream.Length ) + + return String.Empty; + + + + int count = 0; + + long index = m_Stream.Position; + + long start = index; + + + + while ( index < m_Stream.Length && ReadByte() != 0 ) + + ++count; + + + + m_Stream.Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Stream.Position < m_Stream.Length && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + return Encoding.UTF8.GetString( buffer ); + + } + + + + public string ReadString() + + { + + return ReadStringSafe(); + + } + + + + public string ReadStringSafe() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position < m_Stream.Length && ( c = ReadByte() ) != 0 ) + + sb.Append( (char)c ); + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeStringSafe( int fixedLength ) + + { + + return ReadUnicodeString( fixedLength ); + + } + + + + public string ReadUnicodeString( int fixedLength ) + + { + + long bound = m_Stream.Position + ( fixedLength << 1 ); + + long end = bound; + + + + if ( bound > m_Stream.Length ) + + bound = m_Stream.Length; + + + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( ( m_Stream.Position + 1 ) < bound && ( c = ReadUInt16() ) != 0 ) + + if ( IsSafeChar( c ) ) + + sb.Append( (char)c ); + + + + m_Stream.Seek( end, SeekOrigin.Begin ); + + + + return sb.ToString(); + + } + + + + public string ReadStringSafe( int fixedLength ) + + { + + return ReadString( fixedLength ); + + } + + + + public string ReadString( int fixedLength ) + + { + + long bound = m_Stream.Position + fixedLength; + + + + if ( bound > m_Stream.Length ) + + bound = m_Stream.Length; + + + + long end = bound; + + + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Stream.Position < bound && ( c = ReadByte() ) != 0 ) + + sb.Append( (char)c ); + + + + m_Stream.Seek( end, SeekOrigin.Begin ); + + + + return sb.ToString(); + + } + + + + + + + + + ///////////////////////////////////////////// + + ///Packet Writer///////////////////////////// + + ///////////////////////////////////////////// + + public void Write( bool value ) + + { + + m_Stream.WriteByte( (byte)( value ? 1 : 0 ) ); + + } + + + + public void Write( byte value ) + + { + + m_Stream.WriteByte( value ); + + } + + + + public void Write( sbyte value ) + + { + + m_Stream.WriteByte( (byte)value ); + + } + + + + public void Write( short value ) + + { + + m_Buffer[0] = (byte)( value >> 8 ); + + m_Buffer[1] = (byte)value; + + + + m_Stream.Write( m_Buffer, 0, 2 ); + + } + + + + public void Write( ushort value ) + + { + + m_Buffer[0] = (byte)( value >> 8 ); + + m_Buffer[1] = (byte)value; + + + + m_Stream.Write( m_Buffer, 0, 2 ); + + } + + + + public void Write( int value ) + + { + + m_Buffer[0] = (byte)( value >> 24 ); + + m_Buffer[1] = (byte)( value >> 16 ); + + m_Buffer[2] = (byte)( value >> 8 ); + + m_Buffer[3] = (byte)value; + + + + m_Stream.Write( m_Buffer, 0, 4 ); + + } + + + + public void Write( uint value ) + + { + + m_Buffer[0] = (byte)( value >> 24 ); + + m_Buffer[1] = (byte)( value >> 16 ); + + m_Buffer[2] = (byte)( value >> 8 ); + + m_Buffer[3] = (byte)value; + + + + m_Stream.Write( m_Buffer, 0, 4 ); + + } + + + + public void Write( byte[] buffer, int offset, int size ) + + { + + m_Stream.Write( buffer, offset, size ); + + } + + + + public void WriteAsciiFixed( string value, int size ) + + { + + if ( value == null ) + + value = String.Empty; + + + + byte[] buffer = Encoding.ASCII.GetBytes( value ); + + + + if ( buffer.Length >= size ) + + { + + m_Stream.Write( buffer, 0, size ); + + } + + else + + { + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + byte[] pad = new byte[size - buffer.Length]; + + + + m_Stream.Write( pad, 0, pad.Length ); + + } + + } + + + + public void WriteAsciiNull( string value ) + + { + + if ( value == null ) + + value = String.Empty; + + + + byte[] buffer = Encoding.ASCII.GetBytes( value ); + + + + m_Stream.Write( buffer, 0, buffer.Length ); + + m_Stream.WriteByte( 0 ); + + } + + + + public void WriteLittleUniNull( string value ) + + { + + if ( value == null ) + + value = String.Empty; + + + + byte[] buffer = Encoding.Unicode.GetBytes( value ); + + + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + m_Buffer[0] = 0; + + m_Buffer[1] = 0; + + m_Stream.Write( m_Buffer, 0, 2 ); + + } + + + + public void WriteLittleUniFixed( string value, int size ) + + { + + if ( value == null ) + + value = String.Empty; + + + + size *= 2; + + + + byte[] buffer = Encoding.Unicode.GetBytes( value ); + + + + if ( buffer.Length >= size ) + + { + + m_Stream.Write( buffer, 0, size ); + + } + + else + + { + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + byte[] pad = new byte[size - buffer.Length]; + + + + m_Stream.Write( pad, 0, pad.Length ); + + } + + } + + + + public void WriteBigUniNull( string value ) + + { + + if ( value == null ) + + value = String.Empty; + + + + byte[] buffer = Encoding.BigEndianUnicode.GetBytes( value ); + + + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + m_Buffer[0] = 0; + + m_Buffer[1] = 0; + + m_Stream.Write( m_Buffer, 0, 2 ); + + } + + + + public void WriteBigUniFixed( string value, int size ) + + { + + if ( value == null ) + + value = String.Empty; + + + + size *= 2; + + + + byte[] buffer = Encoding.BigEndianUnicode.GetBytes( value ); + + + + if ( buffer.Length >= size ) + + { + + m_Stream.Write( buffer, 0, size ); + + } + + else + + { + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + byte[] pad = new byte[size - buffer.Length]; + + + + m_Stream.Write( pad, 0, pad.Length ); + + } + + } + + + + public void WriteUTF8Fixed( string value, int size ) + + { + + if ( value == null ) + + value = String.Empty; + + + + size *= 2; + + + + byte[] buffer = Encoding.UTF8.GetBytes( value ); + + + + if ( buffer.Length >= size ) + + { + + m_Stream.Write( buffer, 0, size ); + + } + + else + + { + + m_Stream.Write( buffer, 0, buffer.Length ); + + + + byte[] pad = new byte[size - buffer.Length]; + + + + m_Stream.Write( pad, 0, pad.Length ); + + } + + } + + + + public void WriteUTF8Null( string value ) + + { + + if ( value == null ) + + value = String.Empty; + + + + byte[] buffer = Encoding.UTF8.GetBytes( value ); + + + + m_Stream.Write( buffer, 0, buffer.Length ); + + m_Buffer[0] = 0; + + m_Buffer[1] = 0; + + m_Stream.Write( m_Buffer, 0, 2 ); + + } + + + + public void Fill() + + { + + byte[] buffer = new byte[m_Stream.Capacity - Position]; + + m_Stream.Write( buffer, 0, buffer.Length ); + + } + + + + public void Fill( int length ) + + { + + m_Stream.Write( new byte[length], 0, length ); + + } + + + + public int PacketID + + { + + get + + { + + return m_PacketID; + + } + + } + + + + public long Length + + { + + get + + { + + return m_Stream.Length; + + } + + } + + + + public long Position + + { + + get + + { + + return m_Stream.Position; + + } + + set + + { + + m_Stream.Position = value; + + } + + } + + + + public MemoryStream UnderlyingStream + + { + + get + + { + + return m_Stream; + + } + + } + + + + public long Seek( long offset, SeekOrigin origin ) + + { + + return m_Stream.Seek( offset, origin ); + + } + + + + public byte[] ToArray() + + { + + return m_Stream.ToArray(); + + } + + } + + + + public unsafe sealed class PacketReader + + { + + private byte* m_Data; + + private int m_Pos; + + private int m_Length; + + private bool m_Dyn; + + + + public PacketReader( byte* buff, int len, bool dyn ) + + { + + m_Data = buff; + + m_Length = len; + + m_Pos = 0; + + m_Dyn = dyn; + + } + + + + public PacketReader( byte[] buff, bool dyn ) + + { + + fixed ( byte* p = buff ) + + m_Data = p; + + m_Length = buff.Length; + + m_Pos = 0; + + m_Dyn = dyn; + + } + + + + public void MoveToData() + + { + + m_Pos = m_Dyn ? 3 : 1; + + } + + + + public int Seek( int offset, SeekOrigin origin ) + + { + + switch ( origin ) + + { + + case SeekOrigin.End: + + m_Pos = m_Length - offset; + + break; + + case SeekOrigin.Current: + + m_Pos += offset; + + break; + + case SeekOrigin.Begin: + + m_Pos = offset; + + break; + + } + + if ( m_Pos < 0 ) + + m_Pos = 0; + + else if ( m_Pos > m_Length ) + + m_Pos = m_Length; + + return m_Pos; + + } + + + + public int Length { get { return m_Length; } } + + public bool DynamicLength { get { return m_Dyn; } } + + + + public byte[] CopyBytes( int offset, int count ) + + { + + byte[] read = new byte[count]; + + for ( m_Pos = offset; m_Pos < offset + count && m_Pos < m_Length; m_Pos++ ) + + read[m_Pos - offset] = m_Data[m_Pos]; + + return read; + + } + + + + public PacketReader GetCompressedReader() + + { + + int fullLen = ReadInt32(); + + int destLen = 0; + + byte[] buff; + + + + if ( fullLen >= 4 ) + + { + + int packLen = ReadInt32(); + + destLen = packLen; + + + + if ( destLen < 0 ) + + destLen = 0; + + + + buff = new byte[destLen]; + + + + if ( fullLen > 4 && destLen > 0 ) + + { + + if ( ZLib.uncompress( buff, ref destLen, CopyBytes( this.Position, fullLen - 4 ), fullLen - 4 ) != ZLibError.Z_OK ) + + { + + destLen = 0; + + buff = new byte[1]; + + } + + } + + else + + { + + destLen = 0; + + buff = new byte[1]; + + } + + } + + else + + { + + buff = new byte[1]; + + } + + + + return new PacketReader( buff, false ); + + } + + + + public byte ReadByte() + + { + + if ( m_Pos + 1 > m_Length || m_Data == null ) + + return 0; + + return m_Data[m_Pos++]; + + } + + + + public int ReadInt32() + + { + + return ( ReadByte() << 24 ) + + | ( ReadByte() << 16 ) + + | ( ReadByte() << 8 ) + + | ReadByte(); + + } + + + + public short ReadInt16() + + { + + return (short)( ( ReadByte() << 8 ) | ReadByte() ); + + } + + + + public uint ReadUInt32() + + { + + return (uint)( + + ( ReadByte() << 24 ) + + | ( ReadByte() << 16 ) + + | ( ReadByte() << 8 ) + + | ReadByte() ); + + } + + + + public ulong ReadRawUInt64() + + { + + return (ulong) + + ( ( (ulong)ReadByte() << 0 ) + + | ( (ulong)ReadByte() << 8 ) + + | ( (ulong)ReadByte() << 16 ) + + | ( (ulong)ReadByte() << 24 ) + + | ( (ulong)ReadByte() << 32 ) + + | ( (ulong)ReadByte() << 40 ) + + | ( (ulong)ReadByte() << 48 ) + + | ( (ulong)ReadByte() << 56 ) ); + + } + + + + public ushort ReadUInt16() + + { + + return (ushort)( ( ReadByte() << 8 ) | ReadByte() ); + + } + + + + public sbyte ReadSByte() + + { + + if ( m_Pos + 1 > m_Length ) + + return 0; + + return (sbyte)m_Data[m_Pos++]; + + } + + + + public bool ReadBoolean() + + { + + return ( ReadByte() != 0 ); + + } + + + + public string ReadUnicodeStringLE() + + { + + return ReadUnicodeString(); + + } + + + + public string ReadUnicodeStringLESafe() + + { + + return ReadUnicodeStringSafe(); + + } + + + + public string ReadUnicodeStringSafe() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( ( c = ReadUInt16() ) != 0 ) + + { + + if ( IsSafeChar( c ) ) + + sb.Append( (char)c ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeString() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( ( c = ReadUInt16() ) != 0 ) + + sb.Append( (char)c ); + + + + return sb.ToString(); + + } + + + + public bool IsSafeChar( int c ) + + { + + return ( c >= 0x20 && c < 0xFFFE ); + + } + + + + public string ReadUTF8StringSafe( int fixedLength ) + + { + + if ( m_Pos >= m_Length ) + + return String.Empty; + + + + int bound = m_Pos + fixedLength; + + int end = bound; + + + + if ( bound > m_Length ) + + bound = m_Length; + + + + int count = 0; + + int index = m_Pos; + + int start = m_Pos; + + + + while ( index < bound && ReadByte() != 0 ) + + ++count; + + + + Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Pos < bound && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + string s = Encoding.UTF8.GetString( buffer ); + + + + bool isSafe = true; + + + + for ( int i = 0; isSafe && i < s.Length; ++i ) + + isSafe = IsSafeChar( (int)s[i] ); + + + + Seek( start + fixedLength, SeekOrigin.Begin ); + + + + if ( isSafe ) + + return s; + + + + StringBuilder sb = new StringBuilder( s.Length ); + + + + for ( int i = 0; i < s.Length; ++i ) + + { + + if ( IsSafeChar( (int)s[i] ) ) + + sb.Append( s[i] ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUTF8StringSafe() + + { + + if ( m_Pos >= m_Length ) + + return String.Empty; + + + + int count = 0; + + int index = m_Pos; + + int start = index; + + + + while ( index < m_Length && ReadByte() != 0 ) + + ++count; + + + + Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Pos < m_Length && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + string s = Encoding.UTF8.GetString( buffer ); + + + + bool isSafe = true; + + + + for ( int i = 0; isSafe && i < s.Length; ++i ) + + isSafe = IsSafeChar( (int)s[i] ); + + + + if ( isSafe ) + + return s; + + + + StringBuilder sb = new StringBuilder( s.Length ); + + + + for ( int i = 0; i < s.Length; ++i ) + + { + + if ( IsSafeChar( (int)s[i] ) ) + + sb.Append( s[i] ); + + } + + + + return sb.ToString(); + + } + + + + public string ReadUTF8String() + + { + + if ( m_Pos >= m_Length ) + + return String.Empty; + + + + int count = 0; + + int index = m_Pos; + + int start = index; + + + + while ( index < m_Length && ReadByte() != 0 ) + + ++count; + + + + Seek( start, SeekOrigin.Begin ); + + + + index = 0; + + + + byte[] buffer = new byte[count]; + + int value = 0; + + + + while ( m_Pos < m_Length && ( value = ReadByte() ) != 0 ) + + buffer[index++] = (byte)value; + + + + return Encoding.UTF8.GetString( buffer ); + + } + + + + public string ReadString() + + { + + return ReadStringSafe(); + + } + + + + public string ReadStringSafe() + + { + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Pos < m_Length && ( c = ReadByte() ) != 0 ) + + sb.Append( (char)c ); + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeStringSafe( int fixedLength ) + + { + + return ReadUnicodeString( fixedLength ); + + } + + + + public string ReadUnicodeString( int fixedLength ) + + { + + int bound = m_Pos + ( fixedLength << 1 ); + + int end = bound; + + + + if ( bound > m_Length ) + + bound = m_Length; + + + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( ( m_Pos + 1 ) < bound && ( c = ReadUInt16() ) != 0 ) + + if ( IsSafeChar( c ) ) + + sb.Append( (char)c ); + + + + Seek( end, SeekOrigin.Begin ); + + + + return sb.ToString(); + + } + + + + public string ReadUnicodeStringBE( int fixedLength ) + + { + + int bound = m_Pos + ( fixedLength << 1 ); + + int end = bound; + + + + if ( bound > m_Length ) + + bound = m_Length; + + + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( ( m_Pos + 1 ) < bound ) + + { + + c = (ushort)( ReadByte() | ( ReadByte() << 8 ) ); + + sb.Append( (char)c ); + + } + + + + Seek( end, SeekOrigin.Begin ); + + + + return sb.ToString(); + + } + + + + public string ReadStringSafe( int fixedLength ) + + { + + return ReadString( fixedLength ); + + } + + + + public string ReadString( int fixedLength ) + + { + + int bound = m_Pos + fixedLength; + + int end = bound; + + + + if ( bound > m_Length ) + + bound = m_Length; + + + + StringBuilder sb = new StringBuilder(); + + + + int c; + + + + while ( m_Pos < bound && ( c = ReadByte() ) != 0 ) + + sb.Append( (char)c ); + + + + Seek( end, SeekOrigin.Begin ); + + + + return sb.ToString(); + + } + + + + public byte PacketID { get { return *m_Data; } } + + public int Position { get { return m_Pos; } set { m_Pos = value; } } + + + + public bool AtEnd { get { return m_Pos >= m_Length; } } + + } + +} \ No newline at end of file diff --git a/Network/PacketHandler.cs b/Network/PacketHandler.cs new file mode 100644 index 0000000..7d2e045 --- /dev/null +++ b/Network/PacketHandler.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; + +namespace Assistant +{ + public delegate void PacketViewerCallback(PacketReader p, PacketHandlerEventArgs args); + public delegate void PacketFilterCallback(Packet p, PacketHandlerEventArgs args); + + public class PacketHandlerEventArgs + { + private bool m_Block; + public bool Block + { + get { return m_Block; } + set { m_Block = value; } + } + + public PacketHandlerEventArgs() + { + Reinit(); + } + + public void Reinit() + { + m_Block = false; + } + } + + public class PacketHandler + { + private static Dictionary> m_ClientViewers; + private static Dictionary> m_ServerViewers; + + private static Dictionary> m_ClientFilters; + private static Dictionary> m_ServerFilters; + + static PacketHandler() + { + m_ClientViewers = new Dictionary>(); + m_ServerViewers = new Dictionary>(); + + m_ClientFilters = new Dictionary>(); + m_ServerFilters = new Dictionary>(); + } + + internal static void RegisterClientToServerViewer(int packetID, PacketViewerCallback callback) + { + List list; + if (!m_ClientViewers.TryGetValue(packetID, out list) || list == null) + m_ClientViewers[packetID] = list = new List(); + list.Add(callback); + } + + internal static void RegisterServerToClientViewer(int packetID, PacketViewerCallback callback) + { + List list; + if (!m_ServerViewers.TryGetValue(packetID, out list) || list == null) + m_ServerViewers[packetID] = list = new List(); + list.Add(callback); + } + + internal static void RemoveClientToServerViewer(int packetID, PacketViewerCallback callback) + { + List list; + if (m_ClientViewers.TryGetValue(packetID, out list) && list != null) + list.Remove(callback); + } + + internal static void RemoveServerToClientViewer(int packetID, PacketViewerCallback callback) + { + List list; + if (m_ServerViewers.TryGetValue(packetID, out list) && list != null) + list.Remove(callback); + } + + internal static void RegisterClientToServerFilter(int packetID, PacketFilterCallback callback) + { + List list; + if (!m_ClientFilters.TryGetValue(packetID, out list) || list == null) + m_ClientFilters[packetID] = list = new List(); + list.Add(callback); + } + + internal static void RegisterServerToClientFilter(int packetID, PacketFilterCallback callback) + { + List list; + if (!m_ServerFilters.TryGetValue(packetID, out list) || list == null) + m_ServerFilters[packetID] = list = new List(); + list.Add(callback); + } + + internal static void RemoveClientToServerFilter(int packetID, PacketFilterCallback callback) + { + List list; + if (m_ClientFilters.TryGetValue(packetID, out list) && list != null) + list.Remove(callback); + } + + internal static void RemoveServerToClientFilter(int packetID, PacketFilterCallback callback) + { + List list; + if (m_ServerFilters.TryGetValue(packetID, out list) && list != null) + list.Remove(callback); + } + + public static bool OnServerPacket(int id, PacketReader pr, Packet p) + { + bool result = false; + if (pr != null) + { + List list; + if (m_ServerViewers.TryGetValue(id, out list) && list != null && list.Count > 0) + result = ProcessViewers(list, pr); + } + + if (p != null) + { + List list; + if (m_ServerFilters.TryGetValue(id, out list) && list != null && list.Count > 0) + result |= ProcessFilters(list, p); + } + + return result; + } + + public static bool OnClientPacket(int id, PacketReader pr, Packet p) + { + bool result = false; + if (pr != null) + { + List list; + if (m_ClientViewers.TryGetValue(id, out list) && list != null && list.Count > 0) + result = ProcessViewers(list, pr); + } + + if (p != null) + { + List list; + if (m_ClientFilters.TryGetValue(id, out list) && list != null && list.Count > 0) + result |= ProcessFilters(list, p); + } + + return result; + } + + public static bool HasClientViewer(int packetID) + { + List list; + return m_ClientViewers.TryGetValue(packetID, out list) && list != null && list.Count > 0; + } + + public static bool HasServerViewer(int packetID) + { + List list; + return m_ServerViewers.TryGetValue(packetID, out list) && list != null && list.Count > 0; + } + + public static bool HasClientFilter(int packetID) + { + List list; + return (m_ClientFilters.TryGetValue(packetID, out list) && list != null && list.Count > 0); + } + + public static bool HasServerFilter(int packetID) + { + List list; + return (m_ServerFilters.TryGetValue(packetID, out list) && list != null && list.Count > 0); + } + + private static PacketHandlerEventArgs m_Args = new PacketHandlerEventArgs(); + private static bool ProcessViewers(List list, PacketReader p) + { + m_Args.Reinit(); + + if (list != null) + { + int count = list.Count; + for (int i = 0; i < count; i++) + { + p.MoveToData(); + + try + { + list[i](p, m_Args); + } + catch (Exception e) + { + Engine.LogCrash(e); + } + } + } + + return m_Args.Block; + } + + private static bool ProcessFilters(List list, Packet p) + { + m_Args.Reinit(); + + if (list != null) + { + for (int i = 0; i < list.Count; i++) + { + p.MoveToData(); + + try + { + list[i](p, m_Args); + } + catch (Exception e) + { + Engine.LogCrash(e); + } + } + } + + return m_Args.Block; + } + } +} \ No newline at end of file diff --git a/Network/PacketTable.cs b/Network/PacketTable.cs new file mode 100644 index 0000000..4f70c34 --- /dev/null +++ b/Network/PacketTable.cs @@ -0,0 +1,408 @@ +using System; + +namespace Assistant +{ + + public static class PacketsTable + { + private static readonly short[] _packetsTable = new short[255] + { + 0x0068, // 0x00 + 0x0005, // 0x01 + 0x0007, // 0x02 + -1, // 0x03 + 0x0002, // 0x04 + 0x0005, // 0x05 + 0x0005, // 0x06 + 0x0007, // 0x07 + 0x000E, // 0x08 + 0x0005, // 0x09 + 0x000B, // 0x0A + 0x010A, // 0x0B + -1, // 0x0C + 0x0003, // 0x0D + -1, // 0x0E + 0x003D, // 0x0F + 0x00D7, // 0x10 + -1, // 0x11 + -1, // 0x12 + 0x000A, // 0x13 + 0x0006, // 0x14 + 0x0009, // 0x15 + 0x0001, // 0x16 + -1, // 0x17 + -1, // 0x18 + -1, // 0x19 + -1, // 0x1A + 0x0025, // 0x1B + -1, // 0x1C + 0x0005, // 0x1D + 0x0004, // 0x1E + 0x0008, // 0x1F + 0x0013, // 0x20 + 0x0008, // 0x21 + 0x0003, // 0x22 + 0x001A, // 0x23 + 0x0007, // 0x24 + 0x0014, // 0x25 + 0x0005, // 0x26 + 0x0002, // 0x27 + 0x0005, // 0x28 + 0x0001, // 0x29 + 0x0005, // 0x2A + 0x0002, // 0x2B + 0x0002, // 0x2C + 0x0011, // 0x2D + 0x000F, // 0x2E + 0x000A, // 0x2F + 0x0005, // 0x30 + 0x0001, // 0x31 + 0x0002, // 0x32 + 0x0002, // 0x33 + 0x000A, // 0x34 + 0x028D, // 0x35 + -1, // 0x36 + 0x0008, // 0x37 + 0x0007, // 0x38 + 0x0009, // 0x39 + -1, // 0x3A + -1, // 0x3B + -1, // 0x3C + 0x0002, // 0x3D + 0x0025, // 0x3E + -1, // 0x3F + 0x00C9, // 0x40 + -1, // 0x41 + -1, // 0x42 + 0x0229, // 0x43 + 0x02C9, // 0x44 + 0x0005, // 0x45 + -1, // 0x46 + 0x000B, // 0x47 + 0x0049, // 0x48 + 0x005D, // 0x49 + 0x0005, // 0x4A + 0x0009, // 0x4B + -1, // 0x4C + -1, // 0x4D + 0x0006, // 0x4E + 0x0002, // 0x4F + -1, // 0x50 + -1, // 0x51 + -1, // 0x52 + 0x0002, // 0x53 + 0x000C, // 0x54 + 0x0001, // 0x55 + 0x000B, // 0x56 + 0x006E, // 0x57 + 0x006A, // 0x58 + -1, // 0x59 + -1, // 0x5A + 0x0004, // 0x5B + 0x0002, // 0x5C + 0x0049, // 0x5D + -1, // 0x5E + 0x0031, // 0x5F + 0x0005, // 0x60 + 0x0009, // 0x61 + 0x000F, // 0x62 + 0x000D, // 0x63 + 0x0001, // 0x64 + 0x0004, // 0x65 + -1, // 0x66 + 0x0015, // 0x67 + -1, // 0x68 + -1, // 0x69 + 0x0003, // 0x6A + 0x0009, // 0x6B + 0x0013, // 0x6C + 0x0003, // 0x6D + 0x000E, // 0x6E + -1, // 0x6F + 0x001C, // 0x70 + -1, // 0x71 + 0x0005, // 0x72 + 0x0002, // 0x73 + -1, // 0x74 + 0x0023, // 0x75 + 0x0010, // 0x76 + 0x0011, // 0x77 + -1, // 0x78 + 0x0009, // 0x79 + -1, // 0x7A + 0x0002, // 0x7B + -1, // 0x7C + 0x000D, // 0x7D + 0x0002, // 0x7E + -1, // 0x7F + 0x003E, // 0x80 + -1, // 0x81 + 0x0002, // 0x82 + 0x0027, // 0x83 + 0x0045, // 0x84 + 0x0002, // 0x85 + -1, // 0x86 + -1, // 0x87 + 0x0042, // 0x88 + -1, // 0x89 + -1, // 0x8A + -1, // 0x8B + 0x000B, // 0x8C + -1, // 0x8D + -1, // 0x8E + -1, // 0x8F + 0x0013, // 0x90 + 0x0041, // 0x91 + -1, // 0x92 + 0x0063, // 0x93 + -1, // 0x94 + 0x0009, // 0x95 + -1, // 0x96 + 0x0002, // 0x97 + -1, // 0x98 + 0x001A, // 0x99 + -1, // 0x9A + 0x0102, // 0x9B + 0x0135, // 0x9C + 0x0033, // 0x9D + -1, // 0x9E + -1, // 0x9F + 0x0003, // 0xA0 + 0x0009, // 0xA1 + 0x0009, // 0xA2 + 0x0009, // 0xA3 + 0x0095, // 0xA4 + -1, // 0xA5 + -1, // 0xA6 + 0x0004, // 0xA7 + -1, // 0xA8 + -1, // 0xA9 + 0x0005, // 0xAA + -1, // 0xAB + -1, // 0xAC + -1, // 0xAD + -1, // 0xAE + 0x000D, // 0xAF + -1, // 0xB0 + -1, // 0xB1 + -1, // 0xB2 + -1, // 0xB3 + -1, // 0xB4 + 0x0040, // 0xB5 + 0x0009, // 0xB6 + -1, // 0xB7 + -1, // 0xB8 + 0x0003, // 0xB9 //aggiornato da 3 a 5 + 0x0006, // 0xBA + 0x0009, // 0xBB + 0x0003, // 0xBC + -1, // 0xBD + -1, // 0xBE + -1, // 0xBF + 0x0024, // 0xC0 + -1, // 0xC1 + -1, // 0xC2 + -1, // 0xC3 + 0x0006, // 0xC4 + 0x00CB, // 0xC5 + 0x0001, // 0xC6 + 0x0031, // 0xC7 + 0x0002, // 0xC8 + 0x0006, // 0xC9 + 0x0006, // 0xCA + 0x0007, // 0xCB + -1, // 0xCC + 0x0001, // 0xCD + -1, // 0xCE + 0x004E, // 0xCF + -1, // 0xD0 + 0x0002, // 0xD1 + 0x0019, // 0xD2 + -1, // 0xD3 + -1, // 0xD4 + -1, // 0xD5 + -1, // 0xD6 + -1, // 0xD7 + -1, // 0xD8 + 0x010C, // 0xD9 + -1, // 0xDA + -1, // 0xDB + 0x09, // dc + -1, // dd + -1, // de + -1, // df + -1, // e0 + -1, // e1 + 0x0A, // e2 + -1, // e3 + -1, // e4 + -1, // e5 + 0x05, // e6 + 0x0C, // e7 + 0x0D, // e8 + 0x4B, // e9 + 0x03, // ea + -1, // eb + -1, // ec + -1, // ed + 0x0A, // ee + 0x0015, // ef -> mortacci tua + -1, // f0 + 0x09, // f1 + 0x19, // f2 + 0x1A, // f3 -> altro mortacci tua + -1, // f4 + 0x15, // f5 + -1, // f6 + -1, // f7 + 0x6A, // f8 + -1, // f9 + -1, // fa + -1, // fb + -1, // fc + -1, // fd + -1 // ff + }; + + public static short GetPacketLength(int id) + { + return _packetsTable[id]; + } + + public static void AdjustPacketSizeByVersion(ClientVersions version) + { + if (version >= ClientVersions.CV_500A) + { + _packetsTable[0x0B] = 0x07; + _packetsTable[0x16] = -1; + _packetsTable[0x31] = -1; + } + else + { + _packetsTable[0x0B] = 0x10A; + _packetsTable[0x16] = 0x01; + _packetsTable[0x31] = 0x01; + } + + if (version >= ClientVersions.CV_5090) + _packetsTable[0xE1] = -1; + else + _packetsTable[0xE1] = 0x09; + + if (version >= ClientVersions.CV_6013) + { + _packetsTable[0xE3] = -1; + _packetsTable[0xE6] = 0x05; + _packetsTable[0xE7] = 0x0C; + _packetsTable[0xE8] = 0x0D; + _packetsTable[0xE9] = 0x4B; + _packetsTable[0xEA] = 0x03; + } + else + { + _packetsTable[0xE3] = 0x4D; + _packetsTable[0xE6] = -1; + _packetsTable[0xE7] = -1; + _packetsTable[0xE8] = -1; + _packetsTable[0xE9] = -1; + _packetsTable[0xEA] = -1; + } + + if (version >= ClientVersions.CV_6017) + { + _packetsTable[0x08] = 0x0F; + _packetsTable[0x25] = 0x15; + } + else + { + _packetsTable[0x08] = 0x0E; + _packetsTable[0x25] = 0x14; + } + + if (version >= ClientVersions.CV_6060) + { + _packetsTable[0xEE] = 0x2000; + _packetsTable[0xEF] = 0x2000; + _packetsTable[0xF1] = 0x09; + } + else + { + _packetsTable[0xEE] = -1; + _packetsTable[0xEF] = 0x15; + _packetsTable[0xF1] = -1; + } + + if (version >= ClientVersions.CV_60142) + _packetsTable[0xB9] = 0x05; + else + _packetsTable[0xB9] = 0x03; + + if (version >= ClientVersions.CV_7000) + { + _packetsTable[0xEE] = 0x0A; //0x2000; + _packetsTable[0xEF] = 0x15; //0x2000; + } + else + { + _packetsTable[0xEE] = -1; + _packetsTable[0xEF] = 0x15; + } + + if (version >= ClientVersions.CV_7090) + { + _packetsTable[0x24] = 0x09; + _packetsTable[0x99] = 0x1E; + _packetsTable[0xBA] = 0x0A; + _packetsTable[0xF3] = 0x1A; + _packetsTable[0xF1] = 0x09; + _packetsTable[0xF2] = 0x19; + } + else + { + _packetsTable[0x24] = 0x07; + _packetsTable[0x99] = 0x1A; + _packetsTable[0xBA] = 0x06; + _packetsTable[0xF3] = 0x18; + _packetsTable[0xF1] = -1; + _packetsTable[0xF2] = -1; + } + + if (version >= ClientVersions.CV_70180) + _packetsTable[0x00] = 0x6A; + else + _packetsTable[0x00] = 0x68; + } + } + public enum ClientVersions + { + CV_OLD = ( 1 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // Original game + CV_200 = ( 2 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // T2A Introduction. Adds screen dimensions packet + CV_204C = ( 2 << 24 ) | ( 0 << 16 ) | ( 4 << 8 ) | 2, // Adds *.def files + CV_305D = ( 3 << 24 ) | ( 0 << 16 ) | ( 5 << 8 ) | 3, // Renaissance. Expanded character slots. + CV_306E = ( 3 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // Adds a packet with the client type, switches to mp3 from midi for sound files + CV_308D = ( 3 << 24 ) | ( 0 << 16 ) | ( 8 << 8 ) | 3, // Adds maximum stats to the status bar + CV_308J = ( 3 << 24 ) | ( 0 << 16 ) | ( 8 << 8 ) | 9, // Adds followers to the status bar + CV_308Z = ( 3 << 24 ) | ( 0 << 16 ) | ( 8 << 8 ) | 25, // Age of Shadows. Adds paladin, necromancer, custom housing, resists, profession selection window, removes save password checkbox + CV_400B = ( 4 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 1, // Deletes tooltips + CV_405A = ( 4 << 24 ) | ( 0 << 16 ) | ( 5 << 8 ) | 0, // Adds ninja, samurai + CV_4011D = ( 4 << 24 ) | ( 0 << 16 ) | ( 11 << 8 ) | 3, // Adds elven race + CV_500A = ( 5 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // Paperdoll buttons journal becomes quests, chat becomes guild. Use mega FileManager.Cliloc. Removes verdata.mul. + CV_5020 = ( 5 << 24 ) | ( 0 << 16 ) | ( 2 << 8 ) | 0, // Adds buff bar + CV_5090 = ( 5 << 24 ) | ( 0 << 16 ) | ( 9 << 8 ) | 0, // + CV_6000 = ( 6 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // Adds colored guild/all chat and ignore system. New targeting systems, object properties and handles. + CV_6013 = ( 6 << 24 ) | ( 0 << 16 ) | ( 1 << 8 ) | 3, // + CV_6017 = ( 6 << 24 ) | ( 0 << 16 ) | ( 1 << 8 ) | 8, // + CV_6040 = ( 6 << 24 ) | ( 0 << 16 ) | ( 4 << 8 ) | 0, // Increased number of player slots + CV_6060 = ( 6 << 24 ) | ( 0 << 16 ) | ( 6 << 8 ) | 0, // + CV_60142 = ( 6 << 24 ) | ( 0 << 16 ) | ( 14 << 8 ) | 2, // + CV_60144 = ( 6 << 24 ) | ( 0 << 16 ) | ( 14 << 8 ) | 4, // Adds gargoyle race. + CV_7000 = ( 7 << 24 ) | ( 0 << 16 ) | ( 0 << 8 ) | 0, // + CV_7090 = ( 7 << 24 ) | ( 0 << 16 ) | ( 9 << 8 ) | 0, // + CV_70130 = ( 7 << 24 ) | ( 0 << 16 ) | ( 13 << 8 ) | 0, // + CV_70160 = ( 7 << 24 ) | ( 0 << 16 ) | ( 16 << 8 ) | 0, // + CV_70180 = ( 7 << 24 ) | ( 0 << 16 ) | ( 18 << 8 ) | 0, // + CV_70240 = ( 7 << 24 ) | ( 0 << 16 ) | ( 24 << 8 ) | 0, // *.mul -> *.uop + CV_70331 = ( 7 << 24 ) | ( 0 << 16 ) | ( 33 << 8 ) | 1 // + } + +} \ No newline at end of file diff --git a/Network/Packets.cs b/Network/Packets.cs new file mode 100644 index 0000000..f88450e --- /dev/null +++ b/Network/Packets.cs @@ -0,0 +1,1624 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Assistant +{ + public enum MessageType + { + Regular = 0x00, + System = 0x01, + Emote = 0x02, + Label = 0x06, + Focus = 0x07, + Whisper = 0x08, + Yell = 0x09, + Spell = 0x0A, + Encoded = 0xC0, + + Special = 0x20 + } + + public sealed class QueryPartyLocs : Packet + { + public QueryPartyLocs() : base( 0xF0 ) + { + EnsureCapacity( 4 ); + Write( (byte) 0x00 ); + } + } + + public sealed class SendPartyMessage : Packet + { + public SendPartyMessage( string message ) : base(0xBF) + { + EnsureCapacity(1 + 2 + 2 + 1 + 4); + + Write( (ushort)0x06); // party command + Write( (byte)0x04); // tell party + WriteBigUniNull(message); + } + } + + public sealed class AcceptParty : Packet + { + public AcceptParty( Serial leader ) : base( 0xBF ) + { + EnsureCapacity( 1 + 2 + 2 + 1 + 4 ); + + Write( (ushort)0x06 ); // party command + Write( (byte)0x08 ); // accept + Write( (uint)leader ); + } + } + + public sealed class DeclineParty : Packet + { + public DeclineParty( Serial leader ) : base( 0xBF ) + { + EnsureCapacity( 1 + 2 + 2 + 1 + 4 ); + + Write( (ushort)0x06 ); // party command + Write( (byte)0x09 ); // decline + Write( (uint)leader ); + } + } + + public sealed class SendMessageParty : Packet + { + public SendMessageParty(string message) : base(0xBF) + { + EnsureCapacity(1 + 2 + 2 + 1 + 4); + + Write((ushort)0x06); // party command + Write((byte)0x04); // tell party + WriteBigUniNull(message); + } + } + + public sealed class ContainerContent : Packet + { + public ContainerContent(List items ) : this( items, Engine.UsePostKRPackets ) + { + } + + public ContainerContent( List items, bool useKR ) : base( 0x3C ) + { + Write( (ushort)items.Count ); + + foreach ( Item item in items ) + { + Write( (uint)item.Serial ); + Write( (ushort)item.ItemID ); + Write( (sbyte)0 ); + Write( (ushort)item.Amount ); + Write( (ushort)item.Position.X ); + Write( (ushort)item.Position.Y ); + + if ( useKR ) + Write( (byte)item.GridNum ); + + if ( item.Container is Item ) + Write( (uint)((Item)item.Container).Serial ); + else + Write( (uint)0 ); + Write( (ushort)item.Hue ); + } + } + } + + public sealed class ContainerItem : Packet + { + public ContainerItem( Item item ) : this( item, Engine.UsePostKRPackets ) + { + } + + public ContainerItem( Item item, bool isKR ) : base( 0x25, 20 ) + { + if ( isKR ) + EnsureCapacity( 21 ); + + Write( item.Serial ); + + Write( item.ItemID ); + Write( (byte)0 ); + Write( item.Amount ); + Write( (ushort)item.Position.X ); + Write( (ushort)item.Position.Y ); + + if ( isKR ) + Write( item.GridNum ); + + object cont = item.Container; + if ( cont is UOEntity ) + Write( (uint)((UOEntity)item.Container).Serial ); + else if ( cont is uint ) + Write( (uint)cont ); + else if ( cont is Serial ) + Write( (Serial)item.Container ); + else + Write( (uint)0x7FFFFFFF ); + + + Write( item.Hue ); + } + } + + public sealed class SingleClick : Packet + { + public SingleClick( object clicked ) : base( 0x09, 5 ) + { + if ( clicked is Mobile ) + Write( ((Mobile)clicked).Serial ); + else if ( clicked is Item ) + Write( ((Item)clicked).Serial ); + else if ( clicked is Serial ) + Write( ((Serial)clicked).Value ); + else + Write( (uint)0 ); + } + } + + public sealed class DoubleClick : Packet + { + public DoubleClick( Serial clicked ) : base( 0x06, 5 ) + { + Write( (uint)clicked.Value ); + } + } + + public sealed class Target : Packet + { + public Target( uint tid ) : this( tid, false, 0 ) + { + } + + public Target( uint tid , byte flags) : this( tid, false, flags ) + { + } + + public Target( uint tid, bool ground ) : this( tid, ground, 0 ) + { + } + + public Target( uint tid, bool ground, byte flags ) : base( 0x6C, 19 ) + { + Write( ground ); + Write( tid ); + Write( flags ); + Fill(); + } + } + + public sealed class TargetResponse : Packet + { + public TargetResponse( TargetInfo info ) : base( 0x6C, 19 ) + { + Write( (byte) info.Type ); + Write( (uint) info.TargID ); + Write( (byte) info.Flags ); + Write( (uint) info.Serial ); + Write( (ushort) info.X ); + Write( (ushort) info.Y ); + Write( (short) info.Z ); + Write( (ushort) info.Gfx ); + } + + public TargetResponse( uint id, Mobile m ) : base( 0x6C, 19 ) + { + Write( (byte) 0x00 ); // target object + Write( (uint) id ); + Write( (byte) 0 ); // flags + Write( (uint) m.Serial ); + Write( (ushort) m.Position.X ); + Write( (ushort) m.Position.Y ); + Write( (short) m.Position.Z ); + Write( (ushort) m.Body ); + } + + public TargetResponse( uint id, Item item ) : base( 0x6C, 19 ) + { + Write( (byte) 0x00 ); // target object + Write( (uint) id ); + Write( (byte) 0 ); // flags + Write( (uint) item.Serial ); + Write( (ushort) item.Position.X ); + Write( (ushort) item.Position.Y ); + Write( (short) item.Position.Z ); + Write( (ushort) item.ItemID ); + } + } + + public sealed class TargetCancelResponse : Packet + { + public TargetCancelResponse( uint id ) : base( 0x6C, 19 ) + { + Write( (byte) 0 ); + Write( (uint) id ); + Write( (byte) 0 ); + Write( (uint) 0 ); + Write( (ushort) 0xFFFF ); + Write( (ushort) 0xFFFF ); + Write( (short) 0 ); + Write( (ushort) 0 ); + } + } + + public sealed class AttackReq : Packet + { + public AttackReq( Serial serial ) : base( 0x05, 5 ) + { + Write( (uint)serial ); + } + } + + public sealed class SetWeather : Packet + { + public SetWeather(int type, int num) : base(0x65, 4) + { + Write((byte)type); //types: 0x00 - "It starts to rain", 0x01 - "A fierce storm approaches.", 0x02 - "It begins to snow", 0x03 - "A storm is brewing.", 0xFF - None (turns off sound effects), 0xFE (no effect?? Set temperature?) + Write((byte)num); //number of weather effects on screen + Write((byte)0xFE); + } + } + + public sealed class PlayMusic : Packet + { + public PlayMusic(int num) : base(0x6D, 3) + { + Write((uint)num); + } + } + + public sealed class CancelTarget : Packet + { + public CancelTarget( uint id ) : base( 0x6C, 19 ) + { + Write( (byte)0 ); + Write( (uint)id ); + Write( (byte)3 ); + Fill(); + } + } + + public sealed class SkillsQuery : Packet + { + public SkillsQuery( Mobile m ) : base( 0x34, 10 ) + { + Write( (uint)0xEDEDEDED ); // que el fuck, osi + Write( (byte)0x05 ); + Write( m.Serial ); + } + } + + public sealed class StatusQuery : Packet + { + public StatusQuery( Mobile m ) : base( 0x34, 10 ) + { + Write( (uint)0xEDEDEDED ); + Write( (byte)0x04 ); + Write( m.Serial ); + } + } + + public sealed class StatLockInfo : Packet + { + public StatLockInfo( PlayerData m ) : base( 0xBF ) + { + this.EnsureCapacity( 12 ); + + Write( (short) 0x19 ); + Write( (byte) 2 ); + Write( (int) m.Serial ); + Write( (byte) 0 ); + + int lockBits = 0; + + lockBits |= (int)m.StrLock << 4; + lockBits |= (int)m.DexLock << 2; + lockBits |= (int)m.IntLock; + + Write( (byte) lockBits ); + } + } + + public sealed class SkillsList : Packet + { + public SkillsList() : base( 0x3A ) + { + EnsureCapacity( 3 + 1 + Skill.Count*9 + 2 ); + + Write( (byte) 0x02 ); + for (int i=0;i 1 ) + { + Write( (ushort)keys[0] ); + for (int i=1;i list ) : base( 0x9F ) + { + EnsureCapacity( 1 + 2 + 4 + 2 + list.Count*6 ); + + Write( (uint) vendor.Serial ); + Write( (ushort)list.Count ); + + for (int i=0;i list ) : base( 0x3B ) + { + EnsureCapacity( 1 + 2 + 4 + 1 + list.Count * 7 ); + + Write( vendor ); + Write( (byte)0x02 ); // flag + + for(int i=0;i> 24); + m_PrimBuffer[1] = (byte)(value >> 16); + m_PrimBuffer[2] = (byte)(value >> 8); + m_PrimBuffer[3] = (byte) value; + + UnderlyingStream.Write( m_PrimBuffer, 0, 4 ); + } + + public override void Write( short value ) + { + m_PrimBuffer[0] = (byte)(value >> 8); + m_PrimBuffer[1] = (byte) value; + + UnderlyingStream.Write( m_PrimBuffer, 0, 2 ); + } + + public override void Write( byte value ) + { + UnderlyingStream.WriteByte( value ); + } + + public void Write( byte[] buffer, int offset, int size ) + { + UnderlyingStream.Write( buffer, offset, size ); + }*/ + + public static void Clear( byte[] buffer, int size ) + { + for ( int i = 0; i < size; ++i ) + buffer[i] = 0; + } + + public DesignStateDetailed( Serial serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles ) : base( 0xD8 ) + { + EnsureCapacity( 17 + (tiles.Length * 5) ); + + Write( (byte) 0x03 ); // Compression Type + Write( (byte) 0x00 ); // Unknown + Write( (uint) serial ); + Write( (int) revision ); + Write( (short) tiles.Length ); + Write( (short) 0 ); // Buffer length : reserved + Write( (byte) 0 ); // Plane count : reserved + + int totalLength = 1; // includes plane count + + int width = (xMax - xMin) + 1; + int height = (yMax - yMin) + 1; + + if ( m_PlaneBuffers == null ) + { + m_PlaneBuffers = new byte[9][]; + m_PlaneUsed = new bool[9]; + + for ( int i = 0; i < m_PlaneBuffers.Length; ++i ) + m_PlaneBuffers[i] = new byte[0x400]; + + m_StairBuffers = new byte[6][]; + + for ( int i = 0; i < m_StairBuffers.Length; ++i ) + m_StairBuffers[i] = new byte[MaxItemsPerStairBuffer * 5]; + } + else + { + for ( int i = 0; i < m_PlaneUsed.Length; ++i ) + m_PlaneUsed[i] = false; + + Clear( m_PlaneBuffers[0], width * height * 2 ); + + for ( int i = 0; i < 4; ++i ) + { + Clear( m_PlaneBuffers[1 + i], (width - 1) * (height - 2) * 2 ); + Clear( m_PlaneBuffers[5 + i], width * (height - 1) * 2 ); + } + } + + int totalStairsUsed = 0; + + for ( int i = 0; i < tiles.Length; ++i ) + { + MultiTileEntry mte = tiles[i]; + int x = mte.m_OffsetX - xMin; + int y = mte.m_OffsetY - yMin; + int z = mte.m_OffsetZ; + int plane, size; + bool floor = false; + try + { + floor = ( Ultima.TileData.ItemTable[mte.m_ItemID & 0x3FFF].Height <= 0 ); + } + catch + { + } + + switch ( z ) + { + case 0: plane = 0; break; + case 7: plane = 1; break; + case 27: plane = 2; break; + case 47: plane = 3; break; + case 67: plane = 4; break; + default: + { + int stairBufferIndex = ( totalStairsUsed / MaxItemsPerStairBuffer ); + byte[] stairBuffer = m_StairBuffers[stairBufferIndex]; + + int byteIndex = (totalStairsUsed % MaxItemsPerStairBuffer) * 5; + + stairBuffer[byteIndex++] = (byte) ((mte.m_ItemID >> 8) & 0x3F); + stairBuffer[byteIndex++] = (byte) mte.m_ItemID; + + stairBuffer[byteIndex++] = (byte) mte.m_OffsetX; + stairBuffer[byteIndex++] = (byte) mte.m_OffsetY; + stairBuffer[byteIndex++] = (byte) mte.m_OffsetZ; + + ++totalStairsUsed; + + continue; + } + } + + if ( plane == 0 ) + { + size = height; + } + else if ( floor ) + { + size = height - 2; + x -= 1; + y -= 1; + } + else + { + size = height - 1; + plane += 4; + } + + int index = ((x * size) + y) * 2; + + m_PlaneUsed[plane] = true; + m_PlaneBuffers[plane][index] = (byte) ((mte.m_ItemID >> 8) & 0x3F); + m_PlaneBuffers[plane][index + 1] = (byte) mte.m_ItemID; + } + + int planeCount = 0; + + for ( int i = 0; i < m_PlaneBuffers.Length; ++i ) + { + if ( !m_PlaneUsed[i] ) + continue; + + ++planeCount; + + int size = 0; + + if ( i == 0 ) + size = width * height * 2; + else if ( i < 5 ) + size = (width - 1) * (height - 2) * 2; + else + size = width * (height - 1) * 2; + + byte[] inflatedBuffer = m_PlaneBuffers[i]; + + int deflatedLength = m_DeflatedBuffer.Length; + ZLibError ce = ZLib.compress2( m_DeflatedBuffer, ref deflatedLength, inflatedBuffer, size, ZLibCompressionLevel.Z_DEFAULT_COMPRESSION ); + + if ( ce != ZLibError.Z_OK ) + { + Console.WriteLine( "ZLib error: {0} (#{1})", ce, (int)ce ); + deflatedLength = 0; + size = 0; + } + + Write( (byte) (0x20 | i) ); + Write( (byte) size ); + Write( (byte) deflatedLength ); + Write( (byte) (((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)) ); + Write( m_DeflatedBuffer, 0, deflatedLength ); + + totalLength += 4 + deflatedLength; + } + + int totalStairBuffersUsed = ( totalStairsUsed + (MaxItemsPerStairBuffer - 1) ) / MaxItemsPerStairBuffer; + + for ( int i = 0; i < totalStairBuffersUsed; ++i ) + { + ++planeCount; + + int count = ( totalStairsUsed - (i * MaxItemsPerStairBuffer) ); + + if ( count > MaxItemsPerStairBuffer ) + count = MaxItemsPerStairBuffer; + + int size = count * 5; + + byte[] inflatedBuffer = m_StairBuffers[i]; + + int deflatedLength = m_DeflatedBuffer.Length; + ZLibError ce = ZLib.compress2( m_DeflatedBuffer, ref deflatedLength, inflatedBuffer, size, ZLibCompressionLevel.Z_DEFAULT_COMPRESSION ); + + if ( ce != ZLibError.Z_OK ) + { + Console.WriteLine( "ZLib error: {0} (#{1})", ce, (int)ce ); + deflatedLength = 0; + size = 0; + } + + Write( (byte) (9 + i) ); + Write( (byte) size ); + Write( (byte) deflatedLength ); + Write( (byte) (((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)) ); + Write( m_DeflatedBuffer, 0, deflatedLength ); + + totalLength += 4 + deflatedLength; + } + + Seek( 15, System.IO.SeekOrigin.Begin ); + + Write( (short) totalLength ); // Buffer length + Write( (byte) planeCount ); // Plane count + + /*int planes = (tiles.Length + (MaxItemsPerPlane - 1)) / MaxItemsPerPlane; + + if ( planes > 255 ) + planes = 255; + + int totalLength = 0; + + Write( (byte) planes ); + ++totalLength; + + int itemIndex = 0; + + for ( int i = 0; i < planes; ++i ) + { + int byteIndex = 0; + + for ( int j = 0; j < MaxItemsPerPlane && itemIndex < tiles.Length; ++j, ++itemIndex ) + { + MultiTileEntry e = tiles[itemIndex]; + + m_InflatedBuffer[byteIndex++] = (byte)((e.m_ItemID >> 8) & 0x3F); + m_InflatedBuffer[byteIndex++] = (byte)e.m_ItemID; + m_InflatedBuffer[byteIndex++] = (byte)e.m_OffsetX; + m_InflatedBuffer[byteIndex++] = (byte)e.m_OffsetY; + m_InflatedBuffer[byteIndex++] = (byte)e.m_OffsetZ; + } + + int deflatedLength = m_DeflatedBuffer.Length; + ZLibError ce = ZLib.compress2( m_DeflatedBuffer, ref deflatedLength, m_InflatedBuffer, byteIndex, ZLibCompressionLevel.Z_DEFAULT_COMPRESSION ); + + if ( ce != ZLibError.Z_OK ) + { + Console.WriteLine( "ZLib error: {0} (#{1})", ce, (int)ce ); + deflatedLength = 0; + byteIndex = 0; + } + + Write( (byte) 0x00 ); + Write( (byte) byteIndex ); + Write( (byte) deflatedLength ); + Write( (byte) (((byteIndex >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)) ); + Write( m_DeflatedBuffer, 0, deflatedLength ); + + totalLength += 4 + deflatedLength; + } + + Seek( 15, System.IO.SeekOrigin.Begin ); + Write( (short) totalLength ); // Buffer length*/ + } + } +} + + diff --git a/Network/ZLib.cs b/Network/ZLib.cs new file mode 100644 index 0000000..7e0666c --- /dev/null +++ b/Network/ZLib.cs @@ -0,0 +1,659 @@ +using System; + +using System.IO; + +using System.Runtime.InteropServices; + + + +namespace Assistant + +{ + + public enum ZLibError : int + + { + + Z_OK = 0, + + Z_STREAM_END = 1, + + Z_NEED_DICT = 2, + + Z_ERRNO = ( -1 ), + + Z_STREAM_ERROR = ( -2 ), + + Z_DATA_ERROR = ( -3 ), // Data was corrupt + + Z_MEM_ERROR = ( -4 ), // Not Enough Memory + + Z_BUF_ERROR = ( -5 ), // Not enough buffer space + + Z_VERSION_ERROR = ( -6 ) + + } + + + + [Flags] + + public enum ZLibCompressionLevel : int + + { + + Z_NO_COMPRESSION = 0, + + Z_BEST_SPEED = 1, + + Z_BEST_COMPRESSION = 9, + + Z_DEFAULT_COMPRESSION = ( -1 ) + + } + + + + public class ZLib + + { + + [DllImport( "zlib" )] + + internal static extern string zlibVersion(); + + [DllImport( "zlib" )] + + internal static extern ZLibError compress( byte[] dest, ref int destLength, byte[] source, int sourceLength ); + + [DllImport( "zlib" )] + + internal static extern ZLibError compress2( byte[] dest, ref int destLength, byte[] source, int sourceLength, ZLibCompressionLevel level ); + + [DllImport( "zlib" )] + + internal static extern ZLibError uncompress( byte[] dest, ref int destLen, byte[] source, int sourceLen ); + + } + + + + // Be careful when writing raw data, as it may confuse the GZBlockIn if not accounted for when reading. + + // Seeking in the compressed stream is HIGHLY unrecommended + + // If you need to seek, use BufferAll to keep all data in the buffer, seek as much as you want, then + + // turn off BufferAll and flush the data to disk. + + // Once the data is flushed, you CANNOT seek back to it! + + public class GZBlockOut : Stream + + { + + private BinaryWriter m_Out; + + private MemoryStream m_Buffer; + + private BinaryWriter m_Self; + + private int m_BlockSize; + + private bool m_BufferAll; + + private bool m_IsCompressed; + + + + public override bool CanSeek { get { return false; } } + + public override bool CanRead { get { return false; } } + + public override bool CanWrite { get { return true; } } + + public override long Length { get { return RawStream.Length; } } + + public override long Position { get { return m_IsCompressed ? m_Buffer.Position : RawStream.Position; } set { } } + + + + public Stream RawStream { get { return m_Out.BaseStream; } } + + public BinaryWriter Raw { get { return m_Out; } } + + public BinaryWriter Compressed { get { return m_Self; } } + + public MemoryStream Buffer { get { return m_Buffer; } } + + public int BlockSize { get { return m_BlockSize; } set { m_BlockSize = value; } } + + public bool BufferAll { get { return m_BufferAll; } set { m_BufferAll = value; } } + + public bool IsCompressed { get { return m_IsCompressed; } set { ForceFlush(); m_IsCompressed = value; } } + + + + public GZBlockOut( string filename, int blockSize ) + + { + + m_IsCompressed = true; + + + + m_Out = new BinaryWriter( new FileStream( filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None ) ); + + m_BlockSize = blockSize; + + m_Buffer = new MemoryStream( blockSize + 1024 ); + + + + m_Self = new BinaryWriter( this ); + + } + + + + public override void Write( byte[] buffer, int offset, int count ) + + { + + if ( m_IsCompressed ) + + { + + m_Buffer.Write( buffer, offset, count ); + + if ( m_Buffer.Position >= m_BlockSize ) + + FlushBuffer(); + + } + + else + + { + + RawStream.Write( buffer, offset, count ); + + } + + } + + + + public override void WriteByte( byte value ) + + { + + if ( m_IsCompressed ) + + { + + m_Buffer.WriteByte( value ); + + if ( m_Buffer.Position >= m_BlockSize ) + + FlushBuffer(); + + } + + else + + { + + RawStream.WriteByte( value ); + + } + + } + + + + private static byte[] m_CompBuff = null; + + public void FlushBuffer() + + { + + if ( !m_IsCompressed || m_BufferAll || m_Buffer.Position <= 0 ) + + return; + + + + int outLen = (int)( m_Buffer.Position * 1.1 ); + + if ( m_CompBuff == null || m_CompBuff.Length < outLen ) + + m_CompBuff = new byte[outLen]; + + else + + outLen = m_CompBuff.Length; + + + + ZLibError error = ZLib.compress2( m_CompBuff, ref outLen, m_Buffer.ToArray(), (int)m_Buffer.Position, ZLibCompressionLevel.Z_BEST_COMPRESSION ); + + if ( error != ZLibError.Z_OK ) + + throw new Exception( "ZLib error during copression: " + error.ToString() ); + + + + Raw.Write( (int)outLen ); + + Raw.Write( (int)m_Buffer.Position ); + + Raw.Write( m_CompBuff, 0, outLen ); + + + + m_Buffer.Position = 0; + + } + + + + public override void Flush() + + { + + FlushBuffer(); + + RawStream.Flush(); + + } + + + + public void ForceFlush() + + { + + bool old = m_BufferAll; + + m_BufferAll = false; + + Flush(); + + m_BufferAll = old; + + } + + + + public override long Seek( long offset, SeekOrigin origin ) + + { + + if ( m_IsCompressed ) + + return m_Buffer.Seek( offset, origin ); + + else + + return RawStream.Seek( offset, origin ); + + } + + + + public override void SetLength( long value ) + + { + + RawStream.SetLength( value ); + + } + + + + public override int Read( byte[] buffer, int offset, int count ) + + { + + return 0; + + } + + + + public override void Close() + + { + + ForceFlush(); + + + + base.Close(); + + m_Out.Close(); + + m_Buffer.Close(); + + m_Self = null; + + } + + } + + + + // Represents a block compressed stream written by GZBlockOut + + // If there is uncompressed data in the stream, you may seek to + + // it and read from is as you wish using Raw/RawStream. If you have + + // not yet started reading compressed data, you must position rawstream + + // at the begining of the compressed data. If you've already read + + // compressed data, you must reposition the file pointer back to its previous + + // position in the stream. This is really important. + + // + + // Seeking in the compressed stream should be okay, DO NOT attempt to seek outside + + // of the compressed data. + + public class GZBlockIn : Stream + + { + + private MemoryStream m_Uncomp; + + private BinaryReader m_In; + + private BinaryReader m_Self; + + private bool m_Compressed; + + + + public Stream RawStream { get { return m_In.BaseStream; } } + + public BinaryReader Raw { get { return m_In; } } + + public BinaryReader Compressed { get { return m_Compressed ? m_Self : m_In; } } + + public bool IsCompressed { get { return m_Compressed; } set { m_Compressed = value; } } + + + + public override bool CanSeek { get { return true; } } + + public override bool CanRead { get { return true; } } + + public override bool CanWrite { get { return false; } } + + public override long Length { get { return m_Compressed ? ( RawStream.Position < RawStream.Length ? int.MaxValue : m_Uncomp.Length ) : RawStream.Length; } } + + public override long Position { get { return m_Compressed ? m_Uncomp.Position : RawStream.Position; } set { if ( m_Compressed ) m_Uncomp.Position = value; else RawStream.Position = value; } } + + + + public GZBlockIn( string filename ) + + { + + m_Compressed = true; + + + + m_In = new BinaryReader( new FileStream( filename, FileMode.Open, FileAccess.Read, FileShare.Read ) ); + + m_Uncomp = new MemoryStream(); + + m_Self = new BinaryReader( this ); + + } + + + + public override void Write( byte[] buffer, int offset, int count ) + + { + + } + + + + public override void Flush() + + { + + RawStream.Flush(); + + m_Uncomp.Flush(); + + } + + + + private static byte[] m_ReadBuff = null; + + private static byte[] m_CompBuff = null; + + public override long Seek( long offset, SeekOrigin origin ) + + { + + if ( m_Compressed ) + + { + + long absPos = offset; + + if ( origin == SeekOrigin.Current ) + + absPos += m_Uncomp.Position; + + + + if ( absPos < 0 ) + + throw new Exception( "Cannot seek past the begining of the stream." ); + + + + long pos = m_Uncomp.Position; + + m_Uncomp.Seek( 0, SeekOrigin.End ); + + + + while ( ( origin == SeekOrigin.End || absPos >= m_Uncomp.Length ) && RawStream.Position < RawStream.Length ) + + { + + int block = Raw.ReadInt32(); + + int ucLen = Raw.ReadInt32(); + + if ( m_ReadBuff == null || m_ReadBuff.Length < block ) + + m_ReadBuff = new byte[block]; + + + + if ( m_CompBuff == null || m_CompBuff.Length < ucLen ) + + m_CompBuff = new byte[ucLen]; + + else + + ucLen = m_CompBuff.Length; + + + + Raw.Read( m_ReadBuff, 0, block ); + + + + ZLibError error = ZLib.uncompress( m_CompBuff, ref ucLen, m_ReadBuff, block ); + + if ( error != ZLibError.Z_OK ) + + throw new Exception( "ZLib error uncompressing: " + error.ToString() ); + + + + m_Uncomp.Write( m_CompBuff, 0, ucLen ); + + } + + + + m_Uncomp.Position = pos; + + return m_Uncomp.Seek( offset, origin ); + + } + + else + + { + + return RawStream.Seek( offset, origin ); + + } + + } + + + + public override void SetLength( long value ) + + { + + } + + + + public override int Read( byte[] buffer, int offset, int count ) + + { + + if ( m_Compressed ) + + { + + long pos = m_Uncomp.Position; + + m_Uncomp.Seek( 0, SeekOrigin.End ); + + + + while ( pos + count > m_Uncomp.Length && RawStream.Position + 8 < RawStream.Length ) + + { + + int block = Raw.ReadInt32(); + + int ucLen = Raw.ReadInt32(); + + + + if ( block > 0x10000000 || block <= 0 || ucLen > 0x10000000 || ucLen <= 0 ) + + break; + + + + if ( RawStream.Position + block > RawStream.Length ) + + break; + + + + if ( m_ReadBuff == null || m_ReadBuff.Length < block ) + + m_ReadBuff = new byte[block]; + + + + if ( m_CompBuff == null || m_CompBuff.Length < ucLen ) + + m_CompBuff = new byte[ucLen]; + + else + + ucLen = m_CompBuff.Length; + + + + Raw.Read( m_ReadBuff, 0, block ); + + + + ZLibError error = ZLib.uncompress( m_CompBuff, ref ucLen, m_ReadBuff, block ); + + if ( error != ZLibError.Z_OK ) + + throw new Exception( "ZLib error uncompressing: " + error.ToString() ); + + + + m_Uncomp.Write( m_CompBuff, 0, ucLen ); + + } + + + + m_Uncomp.Position = pos; + + return m_Uncomp.Read( buffer, offset, count ); + + } + + else + + { + + return RawStream.Read( buffer, offset, count ); + + } + + } + + + + public override void Close() + + { + + m_In.Close(); + + m_Uncomp.Close(); + + m_Self = null; + + } + + + + public bool EndOfFile + + { + + get + + { + + return ( ( !m_Compressed || m_Uncomp.Position >= m_Uncomp.Length ) && RawStream.Position >= RawStream.Length ); + + } + + } + + } + +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..7513f1b --- /dev/null +++ b/Program.cs @@ -0,0 +1,35 @@ +using CEasyUO; +using CUO_API; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Assistant +{ + static class Program + { + + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + PacketHandlers.Initialize(); + Targeting.Initialize(); EUOVars.Initialize(); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault( false ); + Application.Run( new CEasyUOMainForm() ); + } + + + + + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..deb52b7 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle( "CEasyUO" )] +[assembly: AssemblyDescription( "" )] +[assembly: AssemblyConfiguration( "" )] +[assembly: AssemblyCompany( "" )] +[assembly: AssemblyProduct( "CEasyUO" )] +[assembly: AssemblyCopyright( "Copyright © 2019" )] +[assembly: AssemblyTrademark( "" )] +[assembly: AssemblyCulture( "" )] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible( false )] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid( "003e3eef-9ede-4188-b513-edda5722147b" )] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion( "1.0.0.*" )] +[assembly: AssemblyFileVersion( "1.0.0.*" )] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..7ad673e --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CEasyUO.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( "System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0" )] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute( "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode" )] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( global::System.ComponentModel.EditorBrowsableState.Advanced )] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ( ( resourceMan == null ) ) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager( "CEasyUO.Properties.Resources", typeof( Resources ).Assembly ); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute( global::System.ComponentModel.EditorBrowsableState.Advanced )] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..48f3965 --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CEasyUO.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0" )] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ( (Settings)( global::System.Configuration.ApplicationSettingsBase.Synchronized( new Settings() ) ) ); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Scripting/AST.cs b/Scripting/AST.cs new file mode 100644 index 0000000..4b5515c --- /dev/null +++ b/Scripting/AST.cs @@ -0,0 +1,700 @@ +using Assistant; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CEasyUO +{ + + public enum Result + { + Finished, + Success, + Running + } + public class Stmt { + public int Line = 0; + /// + /// Statements return true if finished, false if run again + /// Blocks return true if they should Execute + /// + /// + public virtual bool Execute() { + return true; + } + } + + abstract class Expr + { + public abstract object GetValue(); + + public int GetValueInt() + { + return int.Parse( GetValue().ToString() ); + } + + } + + public class Block : Stmt + { + public List statements; + + public Block() + { + statements = new List(); + } + + public Stack GetStack() + { + statements.Reverse(); + var res = new Stack( statements ); + statements.Reverse(); + return res; + } + + public void AddStmt(Stmt stmt) + { + statements.Add(stmt); + } + + public override bool Execute() + { + Console.WriteLine( "Executing line: " + Line + " " + this.ToString() ); + return base.Execute(); + } + } + + public abstract class BaseLoopBlock : Block + { + + } + class FindItemStmt : Stmt + { + public List FindIDs = new List(); + public List FindTypes = new List(); + public int Index; + public bool GroundOnly = false; + public bool ContainerOnly = false; + public uint ContainerSerial; + + private Expr Filter; + private Expr Find; + + public FindItemStmt( Expr idType, IntLiteral index, Expr filter ) + { + Find = idType; + + if(index != null) + Index = index.value; + Filter = filter; + } + public override bool Execute() + { + var ids = Find.GetValue().ToString().Split( new[] { '_' } ); + foreach ( var id in ids ) + { + if ( id.Length == 3 ) + FindTypes.Add( Utility.EUO2StealthType( id ) ); + else + FindIDs.Add( Utility.EUO2StealthID( id ) ); + } + if ( Filter != null ) + { + var str = Filter.GetValue().ToString().Trim(); + ContainerOnly = ( str.Contains( "C" ) ); + GroundOnly = ( str.Contains( "G" ) ); + try + { + var id = str.Split( '_' )[1]; + ContainerSerial = Utility.EUO2StealthID( id ); + } + catch { } + } + + var results = new List(); + foreach ( var i in FindIDs ) + results.Add( World.FindEntity( i ) ); + + foreach ( var i in FindTypes ) + results.AddRange( World.Items.Values.Where( t => t.GraphicID == i ) ); + + if ( ContainerOnly ) + results = results.Where( t => t.Parent != null ).ToList(); + if ( GroundOnly ) + results = results.Where( t => t.Parent == null ).ToList(); + var res = results.FirstOrDefault(); + + EUOInterpreter.Setvariable( "#FINDID", Utility.UintToEUO( res?.Serial ?? 0) ); + EUOInterpreter.Setvariable( "#FINDTYPE", Utility.UintToEUO( res?.GraphicID ?? 0) ); + EUOInterpreter.Setvariable( "#findx", res?.Position.X ?? 0 ); + EUOInterpreter.Setvariable( "#findy", res?.Position.Y ?? 0 ); + EUOInterpreter.Setvariable( "#findz", res?.Position.Z ?? 0 ); + return base.Execute(); + } + } + class PauseStmt : Stmt + { + public PauseStmt( ) + { + + + } + } + class WaitStmt : Stmt + { + public Expr Index; + public WaitStmt( Expr index ) + { + Index = index; + + } + public override bool Execute() + { + var timeout = (int)Index.GetValue(); + if ( timeout > 999 ) + Thread.Sleep( timeout ); + else + Thread.Sleep( timeout * 50 ); + return base.Execute(); + } + } + class ScanJournalStmt : Stmt + { + public Expr Index; + public ScanJournalStmt( Expr index) + { + Index = index; + + } + public override bool Execute() + { + var index = (int)Index.GetValue(); + EUOInterpreter.Setvariable( "#journal", EUOInterpreter.GetJournal( index ) ); + return base.Execute(); + } + } + class LinesPerCycle : Stmt + { + public Expr Index; + public LinesPerCycle( Expr index ) + { + Index = index; + + } + } + class IgnoreItemStmt : Stmt + { + public Expr Index; + public IgnoreItemStmt( Expr index ) + { + Index = index; + + } + } + class MenuStmt : Stmt + { + public List Params; + public MenuStmt( List paras ) + { + Params = paras; + } + } + class MessageStmt : Stmt + { + public List Params; + public MessageStmt( List paras ) + { + + Params = paras; + } + public override bool Execute() + { + var msg = ""; + foreach ( var p in Params ) + msg += " " + p.GetValue(); + + ClientCommunication.SendToServer( new ClientUniMessage( MessageType.Regular, World.Player.SpeechHue, 1, "ENU", new System.Collections.ArrayList(), msg ) ); + return base.Execute(); + } + } + class TargetStmt : Stmt + { + public List Params; + public TargetStmt( List paras ) + { + Params = paras; + } + public override bool Execute() + { + var timeout = Params[0].GetValue(); + while ( !Targeting.HasTarget ) + Thread.Sleep( 100 ); + return base.Execute(); + } + } + class ClickStmt : Stmt + { + public List Params; + public ClickStmt( List paras ) + { + Params = paras; + } + public override bool Execute() + { + return base.Execute(); + } + } + class MoveStmt : Stmt + { + public List Params; + public MoveStmt( List paras ) + { + Params = paras; + } + public override bool Execute() + { + var x = Params[0].GetValueInt(); + var y = Params[1].GetValueInt(); + int tolerance = 0; + if(Params.Count>= 3) + tolerance = Params[2].GetValueInt(); + int timeout = 0; + if ( Params.Count >= 4 ) + timeout = Params[3].GetValueInt(); + while(Utility.Distance(World.Player.Position,new Point2D(x,y)) > tolerance ) + { + ClientCommunication.SendToServer( new WalkRequest( World.Player.GetDirectionTo(x,y), World.Player.WalkSequence ) ); + ClientCommunication.SendToClient( new MobileUpdate( World.Player ) ); + Thread.Sleep( 400 ); + } + + return base.Execute(); + } + } + class EventStmt : Stmt + { + public string EventType; + public List Params; + public EventStmt( string eventType, List paras ) + { + EventType = eventType; + Params = paras; + } + + public override bool Execute() + { + switch ( EventType ) + { + case "macro": + switch ( (int)Params[0].GetValue() ) + { + case 13: + ClientCommunication.SendToServer( new UseSkill( (int)Params[1].GetValue() ) ); + break; + case 15: + World.Player.LastSpell = (int)Params[1].GetValue(); + ClientCommunication.CastSpell( (int)Params[1].GetValue() ); + break; + case 16: + ClientCommunication.CastSpell( World.Player.LastSpell ); + break; + case 17: + var obj = Utility.EUO2StealthID( EUOInterpreter.GetVariable( "#lobjectid" ) ); + ClientCommunication.SendToServer( new DoubleClick( obj ) ); + break; + case 22: + var targ = Utility.EUO2StealthID( EUOInterpreter.GetVariable( "#ltargetid" ) ); + ClientCommunication.SendToServer( new TargetResponse( EUOVars.LastTarget ) ); //Targeting.Target( targ ); + ClientCommunication.SendToClient( new CancelTarget( EUOVars.CurrentID ) ); + break; + case 23: + Targeting.Target( World.Player.Serial ); + break; + + } + break; + case "gump": + { + switch ( Params[0].GetValue().ToString() ) + { + case "wait": + { + int timeout = 10000; + if ( Params.Count > 1 ) + timeout = Params[1].GetValueInt(); + int max = timeout / 250; + int cnt = 0; + while ( !World.Player.HasGump && cnt++ < max ) + Thread.Sleep( 250 ); + if ( !World.Player.HasGump ) + World.Player?.SendMessage( "Gump not found" ); + } + break; + case "last": + if ( World.Player?.HasGump == true ) + World.Player?.LastGumpResponseAction?.Perform(); + else + World.Player?.SendMessage( "Gump not found" ); + break; + } + + } + break; + } + return base.Execute(); + } + } + class ExEventStmt : Stmt + { + public string EventType; + public List Params; + public ExEventStmt( string eventType, List paras ) + { + EventType = eventType; + Params = paras; + } + } + class Tile : Stmt + { + public string Command; + public List Params; + public Tile( string command, List paras ) + { + Command = command; + Params = paras; + } + } + + class Goto : Stmt + { + public string Name; + public Goto( string name ) + { + Name = name; + } + } + class Continue : Stmt + { + + public Continue() + { + + } + } + class Break : Stmt + { + + public Break( ) + { + + } + } + class Label : Stmt + { + public string Name; + public Label(string name) + { + Name = name; + } + } + class Func : Block + { + public string ident; + public List vars; + + public Func(string i, List v) + { + ident = i; + vars = v; + } + } + + class WhileBlock : BaseLoopBlock + { + public Expr Expr; + + + + public WhileBlock( Expr lexpr ) + { + Expr = lexpr; + + } + public override bool Execute() + { + return base.Execute(); + } + } + class ForBlock : BaseLoopBlock + { + public Expr From; + public Expr Var; + public Expr To; + + private int Index; + public ForBlock( Expr var, Expr from, Expr to ) + { + From = from; + Var = var; + To = to; + } + public override bool Execute() + { + foreach ( var s in statements ) + { + s.Execute(); + + } + + return base.Execute(); + } + } + + class IfBlock : Block + { + public Expr Expr; + + + public IfBlock(Expr expr) + { + Expr = expr; + + } + public override bool Execute() + { + if(Expr is MathExpr ma) + { + switch(ma.op) + { + case Symbol.Equal: + { + if ( ma.leftExpr.GetValue().Equals( ma.rightExpr.GetValue() ) ) + return true; + return false; + } + case Symbol.NotEqual: + { + if ( ma.leftExpr.GetValue().Equals( ma.rightExpr.GetValue() ) ) + return false; + return true; + } + break; + } + } + return base.Execute(); + } + } + + class ElseIfBlock : Block + { + public Expr leftExpr; + public Symbol op; + public Expr rightExpr; + + public ElseIfBlock(Expr lexpr, Symbol o, Expr rexpr) + { + leftExpr = lexpr; + op = o; + rightExpr = rexpr; + } + } + + class ElseBlock : Block { } + + class EndIf : Block { } + + class RepeatBlock : Block { } + + class Assign : Stmt + { + public Expr ident; + public Expr value; + + public Assign( Expr i, Expr v) + { + ident = i; + value = v; + } + public override bool Execute() + { + string varName = ""; + if ( ident is Ident i ) + { + varName = i.value.ToLowerInvariant(); + EUOInterpreter.Setvariable( varName, value.GetValue() ); + } + else + { + varName = ident.GetValue().ToString().ToLowerInvariant(); + EUOInterpreter.Setvariable( varName, value ); + } + return base.Execute(); + } + } + + class Call : Stmt + { + public string ident; + public List args; + + public Call(string i, List a) + { + ident = i; + args = a; + } + } + + class Return : Stmt + { + public Expr expr; + + public Return(Expr e) + { + expr = e; + } + } + + class IntLiteral : Expr + { + public int value; + + public IntLiteral(int v) + { + value = v; + } + + public override object GetValue() + { + return value; + } + } + + class StringLiteral : Expr + { + public string value; + + public StringLiteral(string v) + { + value = v; + } + public override object GetValue() + { + return value; + } + } + + class Ident : Expr + { + public string value; + + public Ident(string v) + { + value = v; + } + + public override object GetValue() + { + return EUOInterpreter.GetVariable(value.ToLowerInvariant()); + } + } + + class MathExpr : Expr + { + public Expr leftExpr; + public Symbol op; + public Expr rightExpr; + + public MathExpr(Expr lexpr, Symbol o, Expr rexpr) + { + leftExpr = lexpr; + op = o; + rightExpr = rexpr; + } + public override object GetValue() + { + switch (op) + { + case Symbol.add: + if(leftExpr is IntLiteral || rightExpr is IntLiteral) + return (int)leftExpr.GetValueInt() + (int)rightExpr.GetValueInt(); + else + return (string)leftExpr.GetValue() + (string)rightExpr.GetValue(); + case Symbol.sub: + if(leftExpr is IntLiteral || rightExpr is IntLiteral) + return (int)leftExpr.GetValueInt() - (int)rightExpr.GetValueInt(); + break; + case Symbol.mul: + if(leftExpr is IntLiteral || rightExpr is IntLiteral) + return (int)leftExpr.GetValueInt() - (int)rightExpr.GetValueInt(); + break; + case Symbol.div: + if(leftExpr is IntLiteral || rightExpr is IntLiteral) + return (int)leftExpr.GetValueInt() - (int)rightExpr.GetValueInt(); + break; + case Symbol.Concat: + return leftExpr.GetValue().ToString() + rightExpr.GetValue().ToString(); + + } + throw new NotSupportedException(); + } + } + + class ParanExpr : Expr + { + public Expr value; + + public ParanExpr(Expr v) + { + value = v; + } + + public override object GetValue() + { + return value.GetValue(); + } + } + + class CallExpr : Expr + { + public string ident; + public List args; + + public CallExpr(string i, List a) + { + ident = i; + args = a; + } + public override object GetValue() + { + throw new NotImplementedException(); + } + } + + enum Symbol + { + add = 0, + sub = 1, + mul = 2, + div = 3, + doubleEqual = 5, + leftParan = 7, + rightParan = 8, + leftBrace = 9, + rightbrace = 10, + Concat = 11, + Period = 12, + And = 13, + MoreOrEqual = 14, + LessOrEqual = 15, + More = 16, + Equal = 17, + NotEqual = 18, + Less = 19, + Or = 20, + Abs = 21, + In = 22 + } +} diff --git a/Scripting/EUOInterpreter.cs b/Scripting/EUOInterpreter.cs new file mode 100644 index 0000000..8eaab49 --- /dev/null +++ b/Scripting/EUOInterpreter.cs @@ -0,0 +1,565 @@ +using Assistant; +using CEasyUO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + + +namespace CEasyUO +{ + public class EUOInterpreter + { + private List m_Statements = new List(); + public Stmt CurrentStatment; + + + private Dictionary Labels = new Dictionary(); + private Dictionary Subs = new Dictionary(); + private static Dictionary Variables = new Dictionary(); + + private static Stack CurrentStack = null; + + private static Stack> OldStacks = new Stack>(); + + private static Block m_CurBlock; + private static Block CurrentBlock { get { return m_CurBlock; } set { m_CurBlock = value; } } + + public string Script { get; internal set; } + public int CurrentLine => CurrentStatment?.Line ?? 0; + public List AST => m_Statements; + + public bool Paused { get; internal set; } + + private static Stack BlockStack = new Stack(); + + public EUOInterpreter( string script ) + { + Script = script; + var parser = new EUOParser( script ); + m_Statements = parser.GenerateAST(); + } + + Thread ScriptThread; + public bool Running = false; + + public void Run() + { + if ( ScriptThread?.IsAlive == true && Thread.CurrentThread != ScriptThread ) + return; + if ( ScriptThread == null || Thread.CurrentThread != ScriptThread ) + { + ScriptThread = new Thread( new ThreadStart( Run ) ); + ScriptThread.IsBackground = true; + ScriptThread.Start(); + return; + } + Running = true; + while(Running) + { + while ( Paused ) + Thread.Sleep( 50 ); + Statement(); + while ( CurrentStatment != null ) + { + Statement(); + Thread.Sleep( 50 ); + while ( Paused ) + Thread.Sleep( 50 ); + } + Thread.Sleep( 50 ); + } + + + Console.WriteLine( "Script was stopped" ); + return; + } + + public void Stop() + { + if ( !Running ) + return; + Paused = false; + Running = false; + Thread.Sleep( 500 ); + if(ScriptThread.IsAlive) + ScriptThread.Abort(); + ScriptThread = null; + + CurrentStatment = null; + CurrentBlock = null; + CurrentStack = null; + } + + public void Statement() + { + if(CurrentBlock == null && CurrentStack == null) + { + CurrentBlock = m_Statements[0] as Block; + CurrentStack = CurrentBlock.GetStack(); + CurrentStatment = CurrentStack.Pop(); + } + + + if ( CurrentStatment is Goto go ) + { + HandleGoto( go ); + } + else if ( CurrentStatment is Call call ) + { + HandleCall( call ); + } + else if ( CurrentStatment is Break ) + { + //pop till we leave a loop TODO + + CurrentBlock = BlockStack.Pop(); + CurrentStack = OldStacks.Pop(); + CurrentStatment = CurrentStack.Pop(); + } + else if ( CurrentStatment is Return ret ) + { + if ( ret.expr != null ) + Setvariable( "#result", ret.expr.GetValue() ); + //pop till we leave a Func TODO + CurrentBlock = BlockStack.Pop(); + CurrentStack = OldStacks.Pop(); + CurrentStatment = CurrentStack.Pop(); + } + else if ( CurrentStatment is Continue ) + { + CurrentStack = CurrentBlock.GetStack(); + CurrentStatment = CurrentStack.Pop(); + } + else if ( CurrentStatment is Block block ) + { + if ( CurrentStatment.Execute() ) + { + if ( block is BaseLoopBlock loop ) + CurrentStack.Push( block ); + OldStacks.Push( CurrentStack ); + + BlockStack.Push( CurrentBlock ); + CurrentBlock = block; + CurrentStack = CurrentBlock.GetStack(); + CurrentStatment = CurrentStack.Pop(); + //foreach ( var s in CurrentBlock.statements ) + // CurrentStack.Push( s ); + } + else + { + if ( CurrentStack.Count > 0 ) + CurrentStatment = CurrentStack.Pop(); + else + CurrentStatment = null; + } + + } + //DEBUG DONT EXECUTE STATEMENTS + else if(CurrentStatment != null && CurrentStatment.Execute() ) + { + if(CurrentStack.Count > 0) + CurrentStatment = CurrentStack.Pop(); + else + CurrentStatment = null; + } + // + if ( CurrentStatment == null ) + { + if(BlockStack.Count > 0 ) + { + CurrentBlock = BlockStack.Pop(); + CurrentStack = OldStacks.Pop(); + if( CurrentStack.Count > 0) + CurrentStatment = CurrentStack.Pop(); + } + else + { + CurrentBlock = null; + CurrentStack = null; + } + } + } + + private void HandleCall( Call call ) + { + int index = 1; + foreach ( var p in call.args ) + Setvariable( $"%{index++}", p.GetValue() ); + OldStacks.Push( CurrentStack ); + + BlockStack.Push( CurrentBlock ); + var func = ( m_Statements[0] as Block ).statements.FirstOrDefault( t => ( ( t is Func tt ) && tt.ident == call.ident ) ) as Block; + CurrentBlock = func; + CurrentStack = CurrentBlock.GetStack(); + CurrentStatment = CurrentStack.Pop(); + } + + private void HandleGoto( Goto go ) + { + //find the label + //clear current execution stack and set to label and below + var label = FindLabel( go.Name, m_Statements ); + if(label != null ) + { + CurrentStack = label; + } + CurrentStatment = CurrentStack.Pop(); + } + + private Stack FindLabel( string name, List st ) + { + var res = new List(); + var found = false; ; + foreach(var s in st) + { + if ( found ) + { + res.Add( s ); + continue; + } + + if(s is Label lb && lb.Name == name ) + { + found = true; + res.Add( s ); + } + if(s is Block block) + { + var r = FindLabel( name, block.statements ); + if ( r != null) + return r; + } + } + res.Reverse(); + if ( found ) + return new Stack( res); + return null; + } + + internal static string GetJournal( int index ) + { + if ( Journal.Count > index ) + return Journal[index]; + return "X"; + } + + + + + + private Stack IfStack = new Stack(); + + /* private void ParseIF() + { + CurrentIndex++; + if ( CurrentToken.TokenName == Lexer.Tokens.LeftParan ) + { + CurrentIndex++; + } + var lexpr = ParseExpr(); + + var op = CurrentToken; + CurrentIndex++; + var rexpr = ParseExpr(); + if ( CurrentToken.TokenName == Lexer.Tokens.RightParan ) + { + CurrentIndex++; + } + (var startIf, var endIf) = parseBlock(); + + var cnt = IfStack.Count; + + if(op.TokenName == Lexer.Tokens.Equal ) + { + if ( lexpr.GetValue().Equals( rexpr.GetValue() ) ) + { + CurrentIndex = startIf; + if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + else if ( op.TokenName == Lexer.Tokens.NotEqual ) + { + if ( !lexpr.GetValue().Equals( rexpr.GetValue() ) ) + { + CurrentIndex = startIf; if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + else if ( op.TokenName == Lexer.Tokens.MoreOrEqual || op.TokenName == Lexer.Tokens.MoreOrEqual2 ) + { + if ( (int)lexpr.GetValue() >= (int)rexpr.GetValue() ) + { + CurrentIndex = startIf; if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + else if ( op.TokenName == Lexer.Tokens.LessOrEqual || op.TokenName == Lexer.Tokens.LessOrEqual2 ) + { + if ( (int)lexpr.GetValue() <= (int)rexpr.GetValue() ) + { + CurrentIndex = startIf; if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + else if ( op.TokenName == Lexer.Tokens.More ) + { + if ( (int)lexpr.GetValue() > (int)rexpr.GetValue() ) + { + CurrentIndex = startIf; if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + else if ( op.TokenName == Lexer.Tokens.Less ) + { + if ( (int)lexpr.GetValue() < (int)rexpr.GetValue() ) + { + CurrentIndex = startIf; if( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( true ); + } + } + + if ( cnt == IfStack.Count ) // false + if ( CurrentToken.TokenName == Lexer.Tokens.Else || NextToken.TokenName == Lexer.Tokens.Else ) IfStack.Push( false ); + + } + */ + + + private void EventMacro() + { + /* if (CurrentToken.TokenName == Lexer.Tokens.IntLiteral) + { + int idOne = int.Parse(CurrentToken.TokenValue); + int idTwo = 0; + if (NextToken.TokenName == Lexer.Tokens.IntLiteral) + { + CurrentIndex++; + idTwo = int.Parse(CurrentToken.TokenValue); + } + + switch (idOne) + { + case 22: // last target + var targ = Form1.EUO2StealthID(GetVariable("#ltargetid")); + Targeting.Target( targ ); + //Player.Targeting.TargetTo(targ); + break; + case 13: + ClientCommunication.SendToServer( new UseSkill( idTwo ) ); + // Player.UseSkill(idTwo); + break; + + } + + } + else + { + throw new Exception($"Unhandled event {CurrentToken.TokenValue} at line {CurrentLine}"); + }*/ + } + public static T GetVariable(string name) + { + name = name.ToLowerInvariant(); + + try + { + switch ( name ) + { + case "#charname": + return (T)(object)( World.Player?.Name ?? "N/A" ); + case "#sex": + return (T)(object)( World.Player?.Female ); + case "#str": + return (T)(object)( World.Player?.Str.ToString() ?? "0" ); + case "#dex": + return (T)(object)( World.Player?.Dex.ToString() ?? "0" ); + case "#int": + return (T)(object)( World.Player?.Int.ToString() ?? "0" ); + case "#hits": + return (T)(object)( World.Player?.Hits.ToString() ?? "0" ); + case "#maxhits": + return (T)(object)( World.Player?.HitsMax.ToString() ?? "0" ); + case "#stamina": + return (T)(object)( World.Player?.Stam.ToString() ?? "0" ); + case "#maxstam": + return (T)(object)( World.Player?.StamMax.ToString() ?? "0" ); + case "#mana": + return (T)(object)( World.Player?.Mana.ToString() ?? "0" ); + case "#maxmana": + return (T)(object)( World.Player?.ManaMax.ToString() ?? "0" ); + case "#maxstats": + return (T)(object)( World.Player?.StatCap.ToString() ?? "0" ); + case "#luck": + return (T)(object)( World.Player?.Luck.ToString() ?? "0" ); + case "#weight": + return (T)(object)( World.Player?.Weight.ToString() ?? "0" ); + case "#maxweight": + return (T)(object)( World.Player?.MaxWeight.ToString() ?? "0" ); + case "#mindmg": + return (T)(object)( World.Player?.DamageMin.ToString() ?? "0" ); + case "#maxdmg": + return (T)(object)( World.Player?.DamageMax.ToString() ?? "0" ); + case "#gold": + return (T)(object)( World.Player?.Gold.ToString() ?? "0" ); + case "#followers": + return (T)(object)( World.Player?.Followers.ToString() ?? "0" ); + case "#maxfol": + return (T)(object)( World.Player?.FollowersMax.ToString() ?? "0" ); + case "#ar": + return (T)(object)( World.Player?.AR.ToString() ?? "0" ); + case "#fr": + return (T)(object)( World.Player?.FireResistance.ToString() ?? "0" ); + case "#cr": + return (T)(object)( World.Player?.ColdResistance.ToString() ?? "0" ); + case "#pr": + return (T)(object)( World.Player?.PoisonResistance.ToString() ?? "0" ); + case "#er": + return (T)(object)( World.Player?.EnergyResistance.ToString() ?? "0" ); + + + case "#charposx": + return (T)(object)( World.Player?.Position.X.ToString() ?? "0" ); + case "#charposy": + return (T)(object)( World.Player?.Position.Y.ToString() ?? "0" ); + case "#charposz": + return (T)(object)( World.Player?.Position.Z.ToString() ?? "0" ); + case "#chardir": + return (T)(object)( (int)World.Player?.Direction ); + case "#charstatus": + return (T)(object)( World.Player.GetStatusCode() ); + case "#charid": + return (T)(object)Utility.UintToEUO( World.Player.Serial.Value ).ToString(); + case "#chartype": + return (T)(object)Utility.UintToEUO( World.Player.GraphicID ).ToString(); + case "#charghost": + return (T)(object)World.Player.IsGhost.ToString(); + case "#charbackpackid": + return (T)(object)Utility.UintToEUO( World.Player?.Backpack?.Serial ?? 0 ).ToString(); + + + case "#lobjectid": + return (T)(object)Utility.UintToEUO( World.Player.LastObject.Value ).ToString(); + case "#lobjecttype": + return (T)(object)Utility.UintToEUO( World.Player.LastObjectAsEntity?.GraphicID ?? 0 ).ToString(); + case "#ltargetid": + return (T)(object)Utility.UintToEUO( EUOVars.LastTarget?.Serial ?? 0 ).ToString(); + case "#ltargetx": + return (T)(object)( EUOVars.LastTarget?.X.ToString() ?? "0" ); + case "#ltargety": + return (T)(object)( EUOVars.LastTarget?.Y.ToString() ?? "0" ); + case "#ltargetz": + return (T)(object)( EUOVars.LastTarget?.Z.ToString() ?? "0" ); + case "#ltargetkind": + return (T)(object)( EUOVars.LastTarget?.Type ?? 0 ).ToString(); + case "#ltargettile": + return (T)(object)( EUOVars.LastTarget?.Gfx ?? 0 ).ToString(); + case "#lskill": + return (T)(object)World.Player.LastSkill.ToString(); + case "#lspell": + return (T)(object)World.Player.LastSpell.ToString(); + case "#lgumpbutton": + return (T)(object)(World.Player.LastGumpResponseAction?.Button.ToString() ?? "0"); + case "#gumpserial": + return (T)(object)Utility.UintToEUO( World.Player.CurrentGumpS ).ToString(); + case "#gumptype": + return (T)(object)Utility.UintToEUO( World.Player.CurrentGumpI ).ToString(); + + + case "#gumpposx": + return (T)(object)( World.Player.LastGumpX.ToString() ?? "0" ); + case "#gumpposy": + return (T)(object)( World.Player.LastGumpY.ToString() ?? "0" ); + case "#gumpsizex": + return (T)(object)( World.Player.LastGumpWidth.ToString() ?? "0" ); + case "#gumpsizey": + return (T)(object)( World.Player.LastGumpHeight.ToString() ?? "0" ); + + + case "#contkind": + if(World.Player.LastContainerOpenedAt > World.Player.LastGumpOpenedAt) + return (T)(object)Utility.UintToEUO( World.Player.LastContainerGumpGraphic ).ToString(); + else + return (T)(object)Utility.UintToEUO( World.Player.CurrentGumpI ).ToString(); + + case "#contid": + if ( World.Player.LastContainerOpenedAt > World.Player.LastGumpOpenedAt ) + return (T)(object)Utility.UintToEUO( World.Player.LastContainer?.Serial ?? 0 ).ToString(); + else + return (T)(object)Utility.UintToEUO( World.Player.CurrentGumpS ).ToString(); + + case "#conttype":// container item type + if ( World.Player.LastContainerOpenedAt > World.Player.LastGumpOpenedAt ) + return (T)(object)Utility.UintToEUO( World.Player.LastContainer?.GraphicID ?? 0 ).ToString(); + else + return (T)(object)Utility.UintToEUO( World.Player.CurrentGumpI ).ToString(); + + case "#sysmsg": + return (T)(object)( World.Player.LastSystemMessage ?? "N/A" ); + case "#targcurs": + return (T)(object)( Targeting.HasTarget ); + } + } catch + { + return (T)(object)"X"; + } + + + + if (! Variables.ContainsKey( name ) ) + { + Setvariable( name, "X" ); + } + var res = Variables[name]; + if ( res is T result ) + return result; + if(typeof(T) == typeof(string) ) + { + return (T)(object)res.ToString(); + } + return (T)res ; + } + + public static void Setvariable(string key, object value ) + { + key = key.ToLowerInvariant(); + if ( Variables.ContainsKey( key ) ) + Variables[key] = value; + else + Variables.Add( key, value ); + if(value.ToString() != "x") + switch ( key ) + { + case "#lobjectid": + if(World.Player != null) + World.Player.LastObject = Utility.EUO2StealthID( value.ToString() ); + break; + case "#ltargetid": + EUOVars.LastTarget.Serial = Utility.EUO2StealthID( value.ToString() ); + break; + } + } + private void Set() + { + /* CurrentIndex++; + var variableName = ParseExpr(); + string varName = ""; + var value = ParseExpr().GetValue(); + if ( variableName is Ident i ) + { + varName = i.value.ToLowerInvariant(); + Setvariable( varName, value ); + } + else + { + varName = variableName.GetValue().ToString().ToLowerInvariant(); + Setvariable( varName, value ); + } + + */ + + } + + private static List Journal = new List(); + + internal static void AddToJournal( string text ) + { + Journal.Add( text ); + } + } +} \ No newline at end of file diff --git a/Scripting/EUOParser.cs b/Scripting/EUOParser.cs new file mode 100644 index 0000000..3f1574f --- /dev/null +++ b/Scripting/EUOParser.cs @@ -0,0 +1,925 @@ +using CEasyUO.Scripting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CEasyUO +{ + class EUOParser + { + private List m_Tokens; + private int CurrentIndex = 0; + + private Token CurrentToken => m_Tokens.Count >= CurrentIndex ? m_Tokens[CurrentIndex] : null; + private Token NextToken => m_Tokens.Count > CurrentIndex+1 ? m_Tokens[CurrentIndex+1] : null; + private Token LastToken => m_Tokens[CurrentIndex - 1]; + public string Script { get; internal set; } + + public EUOParser(string script ) + { + Script = script; + Lexer lexer = new CEasyUO.Lexer() { InputString = script }; + + List tokens = new List(); + int line = 1; + int tok = 0; + while ( true ) + { + + Token t = lexer.GetToken(); + if ( t == null ) + { + break; + } + + t.Line = line; + if ( t.TokenName == Lexer.Tokens.NewLine ) + line++; + if ( t.TokenName.ToString() == "Undefined" ) + { + var rrr = tokens.Last(); + throw new Exception( $"Undefined token: {t.TokenValue} " ); + } + if ( t.TokenName.ToString() != "Whitespace" && t.TokenName.ToString() != "Undefined" ) + { + tokens.Add( t ); + } + if ( t.TokenName == Lexer.Tokens.Label ) + { + //Labels.Add( t.TokenValue.TrimEnd( new[]{':'} ).ToLowerInvariant(), tok + 1 ); + } + if ( t.TokenName == Lexer.Tokens.Function ) + { + //Subs.Add( t.TokenValue.TrimEnd( new[] { ':' } ).ToLowerInvariant(), tok + 1 ); + } + tok++; + } + m_Tokens = tokens; + m_Tokens.Add( new Token( Lexer.Tokens.NewLine, "" ) ); + + m_Tokens.Add( new Token( Lexer.Tokens.EOF, "" ) ); + if ( m_Tokens == null || m_Tokens.Count <= 1 ) + throw new Exception( "Parse Error" ); + // Player = PlayerMobile.GetPlayer(); + // GenerateAST(); + } + + public List GenerateAST() + { + Block currentBlock = new Block(); + var blockstack = new Stack(); + Token tok = null; + var tree = new List(); + var running = true; + + while ( running ) + { + try + { + tok = CurrentToken; + CurrentIndex++; + if ( tok == null ) + break; + } + catch { } + if ( currentBlock is IfBlock ifb ) + { + if ( BlockEndsAtThisLine != -1 && tok.Line > BlockEndsAtThisLine ) + { + // currentBlock.AddStmt( new EndIf() ); + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + BlockEndsAtThisLine = -1; + } + + } + if ( currentBlock is ForBlock || currentBlock is WhileBlock ) + { + if ( BlockEndsAtThisLine != -1 && tok.Line > BlockEndsAtThisLine ) + { + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + BlockEndsAtThisLine = -1; + } + } + + switch ( tok.TokenName ) + { + case Lexer.Tokens.LeftBrace: + case Lexer.Tokens.Comment: + case Lexer.Tokens.NewLine: + continue; + case Lexer.Tokens.Call: + currentBlock.AddStmt( ParseCall() ); + continue; + case Lexer.Tokens.Tile: + currentBlock.AddStmt( ParseTile() ); + continue; + case Lexer.Tokens.Scanjournal: + currentBlock.AddStmt( ParseScanJournal() ); + continue; + case Lexer.Tokens.ExEvent: + currentBlock.AddStmt( ParseExEvent() ); + continue; + case Lexer.Tokens.Target: + currentBlock.AddStmt( ParseTarget() ); + continue; + case Lexer.Tokens.Msg: + currentBlock.AddStmt( ParseMessage() ); + continue; + case Lexer.Tokens.Move: + currentBlock.AddStmt( ParseMove() ); + continue; + case Lexer.Tokens.Menu: + currentBlock.AddStmt( ParseMenu() ); + continue; + case Lexer.Tokens.Click: + currentBlock.AddStmt( ParseClick() ); + continue; + case Lexer.Tokens.Pause: + currentBlock.AddStmt( new PauseStmt() { Line = tok.Line } ); + continue; + case Lexer.Tokens.Continue: + currentBlock.AddStmt( new Continue() { Line = tok.Line } ); + continue; + case Lexer.Tokens.IgnoreItem: + currentBlock.AddStmt( ParseIgnoreItem() ); + continue; + case Lexer.Tokens.LinesPerCycle: + currentBlock.AddStmt( new LinesPerCycle( ParseExpr() ) { Line = tok.Line } ); + continue; + case Lexer.Tokens.Wait: + currentBlock.AddStmt( new WaitStmt( ParseExpr() ) { Line = tok.Line } ); + continue; + case Lexer.Tokens.Label: + currentBlock.AddStmt( new Label( tok.TokenValue.TrimEnd( new[] { ':' } ).ToLowerInvariant() ) { Line = tok.Line } ); + CurrentIndex++; + continue; + case Lexer.Tokens.Break: + currentBlock.AddStmt( new Break() { Line = tok.Line } ); + CurrentIndex++; + continue; + case Lexer.Tokens.Goto: + { + currentBlock.AddStmt( new Goto( CurrentToken.TokenValue.TrimEnd( new[] { ':' } ).ToLowerInvariant() ) { Line = tok.Line } ); + CurrentIndex++; + + Block block = currentBlock; + + /* if ( blockstack.Count > 0 && block is Func ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + }*/ + + } + continue; + case Lexer.Tokens.Event: + currentBlock.AddStmt( ParseEvent() ); + continue; + case Lexer.Tokens.FindItem: + currentBlock.AddStmt( ParseFindItem() ); + continue; + + } + + if ( tok.TokenName == Lexer.Tokens.Import ) + { + // Program.imports.Add( ParseImport() ); + } + + else if ( tok.TokenName == Lexer.Tokens.Function ) + { + Block block = currentBlock; + if ( blockstack.Count > 0 && block is Func ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + + Func func = ParseFunc(); + + if ( currentBlock != null ) + { + blockstack.Push( currentBlock ); + currentBlock = func; + } + } + else if ( tok.TokenName == Lexer.Tokens.If ) + { + IfBlock ifblock = ParseIf(); + + if ( currentBlock != null ) + { + blockstack.Push( currentBlock ); + currentBlock = ifblock; + } + } + /* else if ( tok.TokenName == Lexer.Tokens.ElseIf ) + { + ElseIfBlock elseifblock = ParseElseIf(); + + if ( currentBlock != null ) + { + blockstack.Push( currentBlock ); + currentBlock = elseifblock; + } + }*/ + else if ( tok.TokenName == Lexer.Tokens.Else ) + { + if ( currentBlock != null ) + { + blockstack.Push( currentBlock ); + currentBlock = new ElseBlock() { Line = tok.Line }; + } + } + else if ( tok.TokenName == Lexer.Tokens.For ) + { + blockstack.Push( currentBlock ); + currentBlock = ParseFor(); + } + else if ( tok.TokenName == Lexer.Tokens.While ) + { + blockstack.Push( currentBlock ); + currentBlock = ParseWhile(); + } + else if ( tok.TokenName == Lexer.Tokens.Repeat ) + { + if ( currentBlock != null ) + { + blockstack.Push( currentBlock ); + currentBlock = new RepeatBlock() { Line = tok.Line }; + } + } + else if ( tok.TokenName == Lexer.Tokens.Set ) + { + Assign a = ParseAssign(); + currentBlock.AddStmt( a ); + } + /*else if ( tok.TokenName == Lexer.Tokens.Ident ) + { + if ( tokens.Peek().TokenName == Lexer.Tokens.Equal ) + { + tokens.pos--; + Assign a = ParseAssign(); + currentBlock.AddStmt( a ); + } + else if ( tokens.Peek().TokenName == Lexer.Tokens.LeftParan ) + { + tokens.pos--; + Call c = ParseCall(); + currentBlock.AddStmt( c ); + } + }*/ + else if ( tok.TokenName == Lexer.Tokens.Return ) + { + Return r = ParseReturn(); + currentBlock.AddStmt( r ); + Block block = currentBlock; + + if ( blockstack.Count > 0 && block is Func ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + else if ( tok.TokenName == Lexer.Tokens.RightBrace ) + { + if ( currentBlock is Func ) + { + currentBlock.AddStmt( new Return( null ) ); + //tree.Add( currentBlock ); + //currentBlock = null; + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + else if ( currentBlock is IfBlock || currentBlock is ElseIfBlock || currentBlock is ElseBlock ) + { + //currentBlock.AddStmt( new EndIf() ); + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + else if ( currentBlock is RepeatBlock ) + { + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + else if ( currentBlock is ForBlock || currentBlock is WhileBlock ) + { + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + } + else if ( tok.TokenName == Lexer.Tokens.EOF ) + { + if ( currentBlock is Func ) + { + currentBlock.AddStmt( new Return( null ) ); + //tree.Add( currentBlock ); + //currentBlock = null; + Block block = currentBlock; + + if ( blockstack.Count > 0 ) + { + currentBlock = blockstack.Pop(); + currentBlock.AddStmt( block ); + } + } + tree.Add( currentBlock ); + running = false; + } + else + { + Console.WriteLine( $"Unhandled Token at Line: {tok.Line} " + tok.TokenName + " " + tok.TokenValue ); + } + } + return tree; + } + + private Stmt ParseMenu() + { + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new MenuStmt( exps ) + { + Line = CurrentToken.Line + }; + } + + private Stmt ParseTarget() + { + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new TargetStmt( exps ) + { + Line = CurrentToken.Line + }; + } + + private Stmt ParseClick() + { + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new ClickStmt( exps ) + { + Line = CurrentToken.Line + }; + } + + private Stmt ParseMove() + { + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new MoveStmt( exps ) { Line = CurrentToken.Line }; + } + + private Stmt ParseMessage() + { + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new MessageStmt(exps ) { Line = CurrentToken.Line }; + } + + private Stmt ParseIgnoreItem() + { + var expr = ParseExpr(); + return new IgnoreItemStmt( expr ) { Line = CurrentToken.Line }; + } + + private Stmt ParseScanJournal() + { + var expr = ParseExpr(); + return new ScanJournalStmt( expr ) { Line = CurrentToken.Line }; + } + + private Block ParseWhile() + { + if ( CurrentToken.TokenName == Lexer.Tokens.LeftParan ) + { + CurrentIndex++; + } + var lexpr = ParseExpr(); + + //var op = CurrentToken; + // CurrentIndex++; + // var rexpr = ParseExpr(); + if ( CurrentToken.TokenName == Lexer.Tokens.RightParan ) + { + CurrentIndex++; + } + BlockEndsAtThisLine = -1; + if ( CurrentToken.TokenName == Lexer.Tokens.IntLiteral ) + { + BlockEndsAtThisLine = int.Parse( CurrentToken.TokenValue ) + CurrentToken.Line; + } + else if ( CurrentToken.TokenName == Lexer.Tokens.LeftBrace || NextToken.TokenName == Lexer.Tokens.LeftBrace ) + { + + } + else + { + BlockEndsAtThisLine = CurrentToken.Line + 1; + } + return new WhileBlock( lexpr ) { Line = CurrentToken.Line }; + } + + private Stmt ParseTile() + { + var command = CurrentToken.TokenValue; + CurrentIndex++; + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + return new Tile( command, exps ) { Line = CurrentToken.Line }; + } + + private Block ParseFor() + { + if ( CurrentToken.TokenName == Lexer.Tokens.LeftParan ) + { + CurrentIndex++; + } + var variable = ParseExpr(); + + var startIndex = ParseExpr(); + + var endIndex = ParseExpr(); + + if ( CurrentToken.TokenName == Lexer.Tokens.RightParan ) + { + CurrentIndex++; + } + BlockEndsAtThisLine = -1; + if ( CurrentToken.TokenName == Lexer.Tokens.IntLiteral ) + { + BlockEndsAtThisLine = int.Parse( CurrentToken.TokenValue ) + CurrentToken.Line; + } + else if ( CurrentToken.TokenName == Lexer.Tokens.LeftBrace || NextToken.TokenName == Lexer.Tokens.LeftBrace ) + { + + } + else + { + BlockEndsAtThisLine = CurrentToken.Line + 1; + } + return new ForBlock( variable, startIndex, endIndex ) { Line = CurrentToken.Line }; + + } + private ExEventStmt ParseExEvent() + { + if ( CurrentToken.TokenName != Lexer.Tokens.StringLiteral ) + { + throw new ParseException(); + } + var type = CurrentToken.TokenValue; + CurrentIndex++; + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + + return new ExEventStmt( type, exps ) { Line = CurrentToken.Line }; + } + private EventStmt ParseEvent() + { + if ( CurrentToken.TokenName != Lexer.Tokens.StringLiteral ) + { + throw new ParseException(); + } + var type = CurrentToken.TokenValue; + CurrentIndex++; + var exps = new List(); + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exps.Add( ParseExpr() ); + + return new EventStmt( type, exps ) { Line = CurrentToken.Line }; + } + + private FindItemStmt ParseFindItem() + { + if ( CurrentToken.TokenName != Lexer.Tokens.StringLiteral ) + { + // throw new Exception(); + } + var ids = ParseExpr(); ;// CurrentToken.TokenValue; + IntLiteral index = null; + Expr filter = null; + if ( CurrentToken.TokenName == Lexer.Tokens.IntLiteral ) + { + index = new IntLiteral(int.Parse(CurrentToken.TokenValue)); + CurrentIndex++; + } + if(CurrentToken.TokenName == Lexer.Tokens.StringLiteral ) + { + filter = ParseExpr(); + } + + return new FindItemStmt( ids, index, filter ) { Line = CurrentToken.Line }; + /* foreach ( var idStr in ids ) + { + if ( idStr.Length == 3 ) + { + var id = Form1.EUO2StealthType( idStr ); + foreach ( var i in World.Items.Values ) + { + if ( i.ItemID == id && ( ( container != 0 && i.ContainerID == container ) || ( ground && i.ContainerID == 0 ) ) ) + { + Setvariable( "#findid", Form1.uintToEUO( i.Serial.Value ) ); + Setvariable( "#findtype", Form1.uintToEUO( i.ItemID.Value ) ); + + Setvariable( "#findx", i.Position.X ); + Setvariable( "#findy", i.Position.Y ); + Setvariable( "#findz", i.Position.Z ); + + } + } + foreach ( var i in World.Mobiles.Values ) + { + if ( i.Body == id ) + { + Setvariable( "#findid", Form1.uintToEUO( i.Serial.Value ) ); + Setvariable( "#findtype", Form1.uintToEUO( i.Body ) ); + + Setvariable( "#findx", i.Position.X ); + Setvariable( "#findy", i.Position.Y ); + Setvariable( "#findz", i.Position.Z ); + + } + } + } + else + { + var id = Form1.EUO2StealthID( idStr ); + if ( World.Items.ContainsKey( id ) ) + { + Setvariable( "#findid", id ); + } + else if ( World.Mobiles.ContainsKey( id ) ) + { + + } + } + + }*/ + } + + + + + private Call ParseCall() + { + var subname = ""; + //if ( CurrentToken.TokenName == Lexer.Tokens.StringLiteral ) + subname = CurrentToken.TokenValue; + var exrps = new List(); + CurrentIndex++; + while ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + exrps.Add( ParseExpr() ); + return new Call( subname, exrps ) { Line = CurrentToken.Line }; + } + + private Assign ParseAssign() + { + var variableName = ParseExpr(); + string varName = ""; + var value = ParseExpr(); + return new Assign( variableName, value ) { Line = CurrentToken.Line }; + + } + + private Return ParseReturn() + { + var exrps = new List(); + if ( CurrentToken.TokenName != Lexer.Tokens.NewLine ) + return new Return( ParseExpr() ) { Line = CurrentToken.Line }; + return new Return( null ) { Line = CurrentToken.Line }; + } + + private int BlockEndsAtThisLine = -1; + private IfBlock ParseIf() + { + if ( CurrentToken.TokenName == Lexer.Tokens.LeftParan ) + { + CurrentIndex++; + } + var lexpr = ParseExpr(); + + //var op = CurrentToken; + // CurrentIndex++; + //var rexpr = ParseExpr(); + if ( CurrentToken.TokenName == Lexer.Tokens.RightParan ) + { + CurrentIndex++; + } + BlockEndsAtThisLine = -1; + if ( CurrentToken.TokenName == Lexer.Tokens.IntLiteral ) + { + BlockEndsAtThisLine = int.Parse( CurrentToken.TokenValue ) + CurrentToken.Line; + } + else if ( CurrentToken.TokenName == Lexer.Tokens.LeftBrace || NextToken.TokenName == Lexer.Tokens.LeftBrace ) + { + + } + else + { + BlockEndsAtThisLine = CurrentToken.Line + 1; + } + return new IfBlock( lexpr ) { Line = CurrentToken.Line }; + + + } + + private Func ParseFunc() + { + string ident = ""; + List vars = new List(); + + if ( CurrentToken.TokenName == Lexer.Tokens.StringLiteral ) + { + ident = CurrentToken.TokenValue.ToString(); + } + else + throw new ParseException( CurrentToken ); + CurrentIndex++; + return new Func( ident, null ) { Line = CurrentToken.Line }; + } + + Expr ParseExpr() + { + Expr ret = null; + Token t = CurrentToken; + + /*if ( NextToken.TokenName == Lexer.Tokens.LeftParan ) + { + string ident = ""; + + if (t.TokenName == Lexer.Tokens.Ident || t.TokenName == Lexer.Tokens.BuildInIdent) + { + ident = t.TokenValue.ToString(); + } + + CurrentIndex++; + + if (NextToken.TokenName == Lexer.Tokens.RightParan) + { + ret = new CallExpr(ident, new List()); + } + else + { + //ret = new CallExpr(ident, ParseCallArgs()); + } + } + else*/ + if ( t.TokenName == Lexer.Tokens.IntLiteral ) + { + if(t.TokenValue.Contains("s")) + ret = new IntLiteral( Convert.ToInt32( t.TokenValue.TrimEnd('s').ToString() )*1000 ); + else + ret = new IntLiteral( Convert.ToInt32( t.TokenValue.ToString() ) ); + + } + else if ( t.TokenName == Lexer.Tokens.StringLiteral ) + { + StringLiteral s = new StringLiteral( t.TokenValue.ToString() ); + ret = s; + } + else if ( t.TokenName == Lexer.Tokens.Ident || t.TokenName == Lexer.Tokens.BuildInIdent ) + { + string ident = t.TokenValue.ToString(); + + Ident i = new Ident( ident ); + ret = i; + } + else if ( t.TokenName == Lexer.Tokens.LeftParan ) + { + CurrentIndex++; + Expr e = ParseExpr(); + + if ( CurrentToken.TokenName == Lexer.Tokens.RightParan ) + { + //CurrentIndex++; + } + + ParanExpr p = new ParanExpr( e ); + + if ( NextToken.TokenName == Lexer.Tokens.Add ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.add, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Sub ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.sub, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Mul ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.mul, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Div ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.div, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Comma ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.Concat, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Period ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.Period, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.And ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.And, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.MoreOrEqual || NextToken.TokenName == Lexer.Tokens.MoreOrEqual2 ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.MoreOrEqual, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.LessOrEqual || NextToken.TokenName == Lexer.Tokens.LessOrEqual2 ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.LessOrEqual, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Less ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.Less, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.More ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.More, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.Equal ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.Equal, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.In ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.In, expr ); + } + else if ( NextToken.TokenName == Lexer.Tokens.NotEqual ) + { + CurrentIndex++; CurrentIndex++; + Expr expr = ParseExpr(); + ret = new MathExpr( p, Symbol.NotEqual, expr ); + } + else + { + ret = p; + } + } + + if (NextToken != null &&( NextToken.TokenName == Lexer.Tokens.Add || NextToken.TokenName == Lexer.Tokens.Sub || NextToken.TokenName == Lexer.Tokens.Mul || NextToken.TokenName == Lexer.Tokens.Div || + NextToken.TokenName == Lexer.Tokens.Comma || NextToken.TokenName == Lexer.Tokens.Period || NextToken.TokenName == Lexer.Tokens.In + || NextToken.TokenName == Lexer.Tokens.LessOrEqual || NextToken.TokenName == Lexer.Tokens.LessOrEqual2 || NextToken.TokenName == Lexer.Tokens.MoreOrEqual || NextToken.TokenName == Lexer.Tokens.MoreOrEqual2 + || NextToken.TokenName == Lexer.Tokens.Less || NextToken.TokenName == Lexer.Tokens.More || NextToken.TokenName == Lexer.Tokens.Equal || NextToken.TokenName == Lexer.Tokens.NotEqual )) + { + Expr lexpr = ret; + Symbol op = 0; + + if ( NextToken.TokenName == Lexer.Tokens.Add ) + { + op = Symbol.add; + } + else if ( NextToken.TokenName == Lexer.Tokens.Sub ) + { + op = Symbol.sub; + } + else if ( NextToken.TokenName == Lexer.Tokens.In ) + { + op = Symbol.In; + } + else if ( NextToken.TokenName == Lexer.Tokens.Mul ) + { + op = Symbol.mul; + } + else if ( NextToken.TokenName == Lexer.Tokens.Div ) + { + op = Symbol.div; + } + else if ( NextToken.TokenName == Lexer.Tokens.Comma ) + { + op = Symbol.Concat; + } + else if ( NextToken.TokenName == Lexer.Tokens.Period ) + { + op = Symbol.Period; + } + else if ( NextToken.TokenName == Lexer.Tokens.MoreOrEqual || NextToken.TokenName == Lexer.Tokens.MoreOrEqual2) + { + op = Symbol.MoreOrEqual; + } + else if ( NextToken.TokenName == Lexer.Tokens.LessOrEqual || NextToken.TokenName == Lexer.Tokens.LessOrEqual2 ) + { + op = Symbol.LessOrEqual; + } + else if ( NextToken.TokenName == Lexer.Tokens.Less ) + { + op = Symbol.Less; + } + else if ( NextToken.TokenName == Lexer.Tokens.More ) + { + op = Symbol.More; + } + else if ( NextToken.TokenName == Lexer.Tokens.Equal ) + { + op = Symbol.Equal; + } + else if ( NextToken.TokenName == Lexer.Tokens.NotEqual ) + { + op = Symbol.NotEqual; + } + CurrentIndex++; + CurrentIndex++; + Expr rexpr = ParseExpr(); + + ret = new MathExpr( lexpr, op, rexpr ); + if ( CurrentToken.TokenName == Lexer.Tokens.And ) + { + lexpr = ret; + CurrentIndex++; + rexpr = ParseExpr(); + ret = new MathExpr( lexpr, Symbol.And, rexpr ); + } else if ( CurrentToken.TokenName == Lexer.Tokens.Or ) + { + lexpr = ret; + CurrentIndex++; + rexpr = ParseExpr(); + ret = new MathExpr( lexpr, Symbol.Or, rexpr ); + } + else if ( CurrentToken.TokenName == Lexer.Tokens.Abs ) + { + lexpr = ret; + CurrentIndex++; + ret = new MathExpr( lexpr, Symbol.Abs, null ); + } + + return ret; + } + + + CurrentIndex++; + return ret; + } + + } +} diff --git a/Scripting/EUOVars.cs b/Scripting/EUOVars.cs new file mode 100644 index 0000000..aa23854 --- /dev/null +++ b/Scripting/EUOVars.cs @@ -0,0 +1,54 @@ +using Assistant; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CEasyUO +{ + class EUOVars + { + public static bool AllowGround { get; private set; } + public static uint CurrentID { get; private set; } + public static byte CurFlags { get; private set; } + public static TargetInfo LastTarget { get; private set; } = new TargetInfo(); + + public static void Initialize() + { + PacketHandler.RegisterClientToServerViewer( 0x6C, new PacketViewerCallback( TargetResponse ) ); + PacketHandler.RegisterServerToClientViewer( 0x6C, new PacketViewerCallback( NewTarget ) ); + PacketHandler.RegisterServerToClientViewer( 0xAA, new PacketViewerCallback( CombatantChange ) ); + } + + + private static void TargetResponse( PacketReader p, PacketHandlerEventArgs args ) + { + TargetInfo info = new TargetInfo(); + info.Type = p.ReadByte(); + info.TargID = p.ReadUInt32(); + info.Flags = p.ReadByte(); + info.Serial = p.ReadUInt32(); + info.X = p.ReadUInt16(); + info.Y = p.ReadUInt16(); + info.Z = p.ReadInt16(); + info.Gfx = p.ReadUInt16(); + LastTarget = info; + } + private static void CombatantChange( PacketReader p, PacketHandlerEventArgs args ) + { + Serial ser = p.ReadUInt32(); + } + + private static void NewTarget( PacketReader p, PacketHandlerEventArgs args ) + { + + AllowGround = p.ReadBoolean(); // allow ground + CurrentID = p.ReadUInt32(); // target uid + CurFlags = p.ReadByte(); // flags + + LastTarget.TargID = CurrentID; + LastTarget.Flags = CurFlags; + } + } +} diff --git a/Scripting/Lexer.cs b/Scripting/Lexer.cs new file mode 100644 index 0000000..07f590c --- /dev/null +++ b/Scripting/Lexer.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace CEasyUO +{ + public class Lexer + { + private readonly Dictionary _tokens; + private readonly Dictionary _regExMatchCollection; + private string _inputString; + private int _index; + + public enum Tokens + { + Undefined = 0, + Import = 1, + Function = 2, + If = 3, + ElseIf = 4, + Else = 5, + While = 6, + Repeat = 7, + Return = 8, + IntLiteral = 9, + StringLiteral = 10, + Ident = 11, + Whitespace = 12, + NewLine = 13, + Add = 14, + Sub = 15, + Mul = 16, + Div = 17, + Equal = 18, + DoubleEqual = 19, + NotEqual = 20, + LeftParan = 21, + RightParan = 22, + LeftBrace = 23, + RightBrace = 24, + Comma = 25, + Period = 26, + EOF = 27, + Set, + Label, + Event, + Goto, + Target, + Wait, + BuildInIdent, + Comment, + LessOrEqual, + MoreOrEqual, + LessOrEqual2, + MoreOrEqual2, + IgnoreItem, + Call, + FindItem, + Msg, + Dollar, + Less, + More, + For, + And, + Or, + GlobalIdent, + UnderScore, + Colon, + Break, + Tile, + Abs, + Scanjournal, + In, + ExEvent, + Move, + Click, + Pause, + LinesPerCycle, + Menu, + Continue + } + + public string InputString + { + set + { + _inputString = value.ToLowerInvariant(); + PrepareRegex(); + } + } + + public Lexer() + { + _tokens = new Dictionary(); + _regExMatchCollection = new Dictionary(); + _index = 0; + _inputString = string.Empty; + + + _tokens.Add( Tokens.Set, "set " ); + _tokens.Add( Tokens.Menu, "menu " ); + _tokens.Add( Tokens.Continue, "continue" ); + _tokens.Add( Tokens.Event, "event " ); + _tokens.Add( Tokens.ExEvent, "exevent " ); + _tokens.Add( Tokens.Goto, "goto " ); + _tokens.Add( Tokens.Call, "gosub " ); + _tokens.Add( Tokens.Function, "sub " ); + _tokens.Add( Tokens.FindItem, "finditem " ); + _tokens.Add( Tokens.Msg, "msg " ); + _tokens.Add( Tokens.Pause, "pause" ); + _tokens.Add( Tokens.LinesPerCycle, "linespercycle" ); + _tokens.Add( Tokens.Click, "click" ); + _tokens.Add( Tokens.Move, "move " ); + _tokens.Add( Tokens.Break, "break" ); + _tokens.Add( Tokens.Tile, "tile " ); + _tokens.Add( Tokens.Target, "target" ); + _tokens.Add( Tokens.Wait, "wait " ); + _tokens.Add( Tokens.IgnoreItem, "ignoreitem " ); + + _tokens.Add(Tokens.Scanjournal, "scanjournal " ); + _tokens.Add(Tokens.For, "for "); + _tokens.Add( Tokens.While, "while " ); + _tokens.Add( Tokens.Abs, "[ \\t]+abs" ); + _tokens.Add( Tokens.If, "if " ); + _tokens.Add( Tokens.In, " in " ); + _tokens.Add( Tokens.ElseIf, "elseif" ); + _tokens.Add( Tokens.Else, "else" ); + _tokens.Add( Tokens.Repeat, "repeat" ); + _tokens.Add( Tokens.Return, "return" ); + _tokens.Add( Tokens.IntLiteral, "[-+]?[0-9]+[s]?" ); + + _tokens.Add( Tokens.Label, "[a-zA-Z_][a-zA-Z0-9_]*?:" ); + _tokens.Add( Tokens.Comment, ";[^\n\r]*" ); + _tokens.Add( Tokens.GlobalIdent, "![a-zA-Z0-9_]+" ); + _tokens.Add( Tokens.Ident, "%[a-zA-Z0-9_]+" ); + _tokens.Add( Tokens.BuildInIdent, "#[a-zA-Z0-9_]+" ); + _tokens.Add( Tokens.StringLiteral, "[a-zA-Z0-9_:']+[a-zA-Z0-9_:']*" );// "[a-zA-Z_:'][a-zA-Z0-9_:']*" ); + _tokens.Add( Tokens.Whitespace, "[ \\t]+" ); + _tokens.Add( Tokens.NewLine, "\\r?\\n" ); + _tokens.Add( Tokens.UnderScore, "_" ); + _tokens.Add( Tokens.Add, "\\+" ); + _tokens.Add( Tokens.Sub, "\\-" ); + _tokens.Add( Tokens.Mul, "\\*" ); + _tokens.Add( Tokens.Div, "\\/" ); + _tokens.Add( Tokens.NotEqual, "\\<>" ); + _tokens.Add( Tokens.LessOrEqual, "\\<=" ); + _tokens.Add( Tokens.MoreOrEqual, "\\>=" ); + _tokens.Add( Tokens.LessOrEqual2, "=\\<" ); + _tokens.Add( Tokens.MoreOrEqual2, "=\\>" ); + _tokens.Add( Tokens.Less, "\\<" ); + _tokens.Add( Tokens.More, "\\>" ); + _tokens.Add( Tokens.And, "\\&\\&" ); + + _tokens.Add( Tokens.Or, "\\|\\|" ); + _tokens.Add( Tokens.Equal, "\\=" ); + _tokens.Add( Tokens.LeftParan, "\\(" ); + _tokens.Add( Tokens.RightParan, "\\)" ); + _tokens.Add( Tokens.LeftBrace, "\\{" ); + _tokens.Add( Tokens.RightBrace, "\\}" ); + _tokens.Add( Tokens.Comma, "\\," ); + _tokens.Add( Tokens.Period, "\\." ); + _tokens.Add( Tokens.Dollar, "\\$" ); + + } + + private void PrepareRegex() + { + _regExMatchCollection.Clear(); + foreach ( KeyValuePair pair in _tokens ) + { + _regExMatchCollection.Add( pair.Key, Regex.Matches( _inputString, pair.Value ) ); + } + } + + public void ResetParser() + { + _index = 0; + _inputString = string.Empty; + _regExMatchCollection.Clear(); + } + + public Token GetToken() + { + if ( _index >= _inputString.Length ) + return null; + + foreach ( KeyValuePair pair in _regExMatchCollection ) + { + foreach ( Match match in pair.Value ) + { + if ( match.Index == _index ) + { + _index += match.Length; + return new Token( pair.Key, match.Value, match.Index ); + } + + if ( match.Index > _index ) + { + break; + } + } + } + _index++; + return new Token( Tokens.Undefined, _inputString.Substring(_index-1,_inputString.Length - _index) ); + } + + public PeekToken Peek() + { + return Peek( new PeekToken( _index, new Token( Tokens.Undefined, string.Empty ) ) ); + } + + public PeekToken Peek( PeekToken peekToken ) + { + int oldIndex = _index; + + _index = peekToken.TokenIndex; + + if ( _index >= _inputString.Length ) + { + _index = oldIndex; + return null; + } + + foreach ( KeyValuePair pair in _tokens ) + { + Regex r = new Regex( pair.Value ); + Match m = r.Match( _inputString, _index ); + + if ( m.Success && m.Index == _index ) + { + _index += m.Length; + PeekToken pt = new PeekToken( _index, new Token( pair.Key, m.Value ) ); + _index = oldIndex; + return pt; + } + } + PeekToken pt2 = new PeekToken( _index + 1, new Token( Tokens.Undefined, string.Empty ) ); + _index = oldIndex; + return pt2; + } + } + + public class PeekToken + { + public int TokenIndex { get; set; } + + public Token TokenPeek { get; set; } + + public PeekToken( int index, Token value ) + { + TokenIndex = index; + TokenPeek = value; + } + } + + public class Token + { + public Lexer.Tokens TokenName { get; set; } + + public string TokenValue { get; set; } + public int Line { get; internal set; } + + public int Index; + + public Token( Lexer.Tokens name, string value, int index = -1 ) + { + TokenName = name; + TokenValue = value; + Index = index; + } + + public override string ToString() + { + return TokenName.ToString(); + } + } + + public class TokenList + { + public List Tokens; + public int pos = 0; + + public TokenList( List tokens ) + { + Tokens = tokens; + } + + public Token GetToken() + { + Token ret = Tokens[pos]; + pos++; + return ret; + } + + public Token Peek() + { + return Tokens[pos]; + } + } +} diff --git a/Scripting/ParseException.cs b/Scripting/ParseException.cs new file mode 100644 index 0000000..216479f --- /dev/null +++ b/Scripting/ParseException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace CEasyUO +{ + [Serializable] + internal class ParseException : Exception + { + private Token currentToken; + + public ParseException() + { + } + + public ParseException( Token c ) : base( $"Error Parsing Token at: {c.Line} Token identified as: {c.TokenName} value: {c.TokenValue}" ) + { + this.currentToken = currentToken; + } + + + protected ParseException( SerializationInfo info, StreamingContext context ) : base( info, context ) + { + } + } +} \ No newline at end of file diff --git a/Scripting/SpracheParser.cs b/Scripting/SpracheParser.cs new file mode 100644 index 0000000..c3fd25d --- /dev/null +++ b/Scripting/SpracheParser.cs @@ -0,0 +1,37 @@ +using Sprache; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CEasyUO.Scripting +{ + + /* class SpracheParser + { + public static readonly Parser Ident = + ( from open in Parse.Chars(new[] {'%','#','!' } ) + from content in Parse.Letter.AtLeastOnce().Text() + select content ).Token(); + + + public static readonly Parser<(string,string)> Assign = + from set in Parse.String("set") + from id in Ident + from val in STRINGORNUM + select ( id, val ); + + + public static readonly Parser String = Parse.Letter.AtLeastOnce().Text().Token(); + + public static readonly Parser STRINGORNUM = Parse.Letter.AtLeastOnce().Text().Token().Or(Parse.Number); + + public SpracheParser(string input) + { + + // var res = Assign.Parse( input ); + // res = Assign.Parse( input ); + } + }*/ +} diff --git a/Ultima.dll b/Ultima.dll new file mode 100644 index 0000000000000000000000000000000000000000..ea770b2dccb22efff42dd4f8956a0f8c416a170c GIT binary patch literal 163840 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 literal 0 HcmV?d00001 diff --git a/icons/clinew.ico b/icons/clinew.ico new file mode 100644 index 0000000000000000000000000000000000000000..2451e014cf46585558f4b18cd237f6469d048e7e GIT binary patch literal 318 zcmb7;u?+$-5Cq2(i8DvUNF+2xL?bTY0+P?64dm!MpaO~@G`nxTNcey|@6Ks0%Lhqa zO2KtXus0U$)H^V92*&W-3RviX?f0W+MKL6G z&Y7qo!9G-oL9Sw$=>6k2Oq^kwKhoS=>}XsTM@1R%a&=1P1jUSOVEya4&%AyIuW`>8>bFCb literal 0 HcmV?d00001 diff --git a/icons/copy.ico b/icons/copy.ico new file mode 100644 index 0000000000000000000000000000000000000000..b69fc922f848dbb6f3bfb1b27501686517b7bd5a GIT binary patch literal 318 zcmZ{fOAf*?3`8d?QkTh!HA{|}qvc3B0Frs*&~^bhp66dH*;3)P)?|AY>F$W!d7z>j zlt%Iwx`eCu6(A|<8e=esbVWErJhzPhRzLG9;lMS>+BoZ(U(}d0=aDtYpJ=Y=@MUWc X>BZfLIM_#D+Q@h0HCVZzE%%q+%Sb&< literal 0 HcmV?d00001 diff --git a/icons/cut.ico b/icons/cut.ico new file mode 100644 index 0000000000000000000000000000000000000000..761b6fe9eed095842660a5037b452e230ea7a53b GIT binary patch literal 318 zcmc(Zu?+%23WcPEq{S^gY;VG;9PTz8)Gc7oRp-rCulSSQ#X HUwr!kA0017 literal 0 HcmV?d00001 diff --git a/icons/easyuo2.ico b/icons/easyuo2.ico new file mode 100644 index 0000000000000000000000000000000000000000..2f7561c8cf7b82e9e7659f4897ec026976ee609b GIT binary patch literal 2238 zcma)7U1$_n6h6DNYK-EBm^Su75-<}azN8?VlrHR2m$r(H3U=CprDgOXByKA0K+|^F z9Y~d8p`}o0=;R@Yl-j3&LVS^Kp=tzQd=qJ5pNrrYq^>nPh;U1y<4D%xFUg$8i2u+E`Bm*as6c*3`^3~&hdDB~{^o+U{`U3l!=~fm& z8N2sg<>rW9Gj$Q%&)+FK#N>mrvweKdSy_ESmeX71%hVkN@2EB7r@(cf2P({y=_ozHS&ig*_g7Z^p5pJRkRbu1bixNm<*LM<_4#&~hE=@>|zr zdFi^a#$Rb(d|pbZO?y<;(%gR(hA@O&$_d=vA$Llh9q(onw2oO%BmWBYr@zb2_~4AX zOU~&cmbx-gT`^qs#4^&$GV(j(FYlane!vZLg3ZQe$-(D3;&2|~i{Mg5)^+_Nc)Ta_ z_q;S&jq()&y%unFhyc#Lws z@RtO^fI1KsP?=Fg7XU%mfWh{od`1ya=a0d_(x(&0Jzw(-TU(~b=~+OCXc!G&Q;(76r$LjI2$nK+D5Bjy%q$`p&+Pl z2?E!^7i}uQ`3CF!O5V{Zw1%40Elp@6+b|^kp@DCMJ(Js;Tl1O5L41oG#Q&6A(4~T# eS}GtP>p;?Ot(9#Qkf$qzm0E>XVST8nFxscq3`-dR literal 0 HcmV?d00001 diff --git a/icons/find.ico b/icons/find.ico new file mode 100644 index 0000000000000000000000000000000000000000..c4d588b52c4c90c3c82c61779f5dd9490fe29056 GIT binary patch literal 318 zcmZvXO9}!p5JW3PP*6x#ZguI>8_dxrN6TH@>p%|BR&~!XASS7L>F#`i#9nKK+9Ls1 zky7SPB^>BLcmHP%;6Q5uI;T`a@0|tn0W-}gcBN!LtuSc|AOcTunMM|~y*w^9yk0)R q{2tr7ZC`LdC*s1p;@tgGf5L_FiQ?apSDi^o@saY)D{tgP&A=CHyGthk literal 0 HcmV?d00001 diff --git a/icons/help.ico b/icons/help.ico new file mode 100644 index 0000000000000000000000000000000000000000..1a6fdcd14009bca50574c956df3229cacd57e754 GIT binary patch literal 318 zcmZvYu?@m75Ji6k7IdVdg^~rRC|Ds9I#GIsH0j;JLR4uyN=9%KDFq@t8#*}q|2sdQ zEz45HsOwre4wbac8D+9Zm6o^^i~qAAF%`lm;gl#S#>mE8rsg?Uh>=mY-*DH$oS#|> z=hONobw0V~-l|K^4A43|8K5(}0OUrq-WtgRdgI(^$1ms~tV>6^*yk1D=q~Sgouz3f a4IAFP0xl5$P=Xv#L<1n9iTx+N`P>`q06pXY literal 0 HcmV?d00001 diff --git a/icons/home.ico b/icons/home.ico new file mode 100644 index 0000000000000000000000000000000000000000..93863b0c8c5b656f9c7d58583d54e8ea84fbabc6 GIT binary patch literal 318 zcmZ{fu?<2o3`8#ii6X^MMNJ2ru|`J92p)wIeo28a@A4%I#CLLc_Bn}SsjymWa@>k^ zwgNjBP;`W)nEa2r#8&T{z@(rnB535?%5(-;woJdVWF5Im`^v@H=Q=u&tWsSl2d`mJ k+L$FqWhNVAQxEhFe<2S0=$v)`agtW>1h1>~zWe{u2beob@Bjb+ literal 0 HcmV?d00001 diff --git a/icons/new.ico b/icons/new.ico new file mode 100644 index 0000000000000000000000000000000000000000..10d80b7afc762ce285a535a10f5e7c9b956259fd GIT binary patch literal 318 zcmbu4OAde_3`B>-#3iiU>e5?yHICLJc>{a{JEMt-ZD*z*AVo@eDMhwDNsAS@vw@@u zmZI|$Rf$loLts)+6%hzj=|_}lyunR6>RyuWk- D=gd%V literal 0 HcmV?d00001 diff --git a/icons/open.ico b/icons/open.ico new file mode 100644 index 0000000000000000000000000000000000000000..e4405da561ac51b66a4340129d12f6ee0a6538dc GIT binary patch literal 318 zcmb7=F%AMD6hw!ti3J5zwrgo=;@RxE!Cqx=uv}?Nvjx5ZOD!CD^FKpKevkqy=Pc_M zq>C#!xkAthmumNKRwTAc83m_>qFO5#t#7sF4p@$Czw|(NDNb&rw2mMiBS_oYU&aH` hr43P|4}WOP!GjqRoM8g~q09M)WoPCWJYn(t(i`@HF}VN$ literal 0 HcmV?d00001 diff --git a/icons/openeuo.ico b/icons/openeuo.ico new file mode 100644 index 0000000000000000000000000000000000000000..994b6ace91ae78434d8ee3a8b153a8611014ccb0 GIT binary patch literal 4286 zcmds2K~lpo40PeZt>wfuJ@Sk2v*hcmCVX}_$G!s_I~-aOV+TEmQc!E5}sVgP4-1?I5EjkYHvZ)|*?WOP5J@ko}OZc5rF6^c*Cx( zD0S1;a2C4>4hhVc;Hg-aFic#7@f6d$2Y{ZT6d-a{KhB;6d?H-a*jcdo#;a;6%xyYmb3jn2i!1m{17mdN zmgpO$F3e_o(JL~A4n2=D?G^veNI{la{|IVK-*Z>aI7|BU4dmRX&H+@^hF{fS^DprY zuJK;$PIUx5ru$m%t#~T8)hnUFW*FD9`dii*XMJqJS^b}~-p=XP zI;x##8i3#EpE~*7R0kh2On@Ptb#iUG^N9rn`Kmqh?2yjx0QIMwVaqoR>-=T&W8-~a zdEdTxFVE<9=fCjzN9USh-_=9atM^r3b%*Z2)nenmYnaD;;%4|KX@)~rGyhcKgc|Ch K9;;s8as39Qklm#K literal 0 HcmV?d00001 diff --git a/icons/paste.ico b/icons/paste.ico new file mode 100644 index 0000000000000000000000000000000000000000..c3d4b89a0cbc2ccc3bcc7b6b73f2fa59c823d896 GIT binary patch literal 318 zcmZvYu?_+u5JZP?iG>AJS6f<=@PY0pU2nrDSz4Ij+tnDOx691z&H^DwiLABC@O#IEa_My2m}5@HX%Kz_0iNd6HK{vg{0+hzV_H+G(Kx7`k%~vabQ& z%7i)}!r(;)V&N7AfPoOsL}p0@#27iqC6Kc!g07aRGU{EDRPsZ6KS|rpPn!4ZrBiq6 lZPmWrrd00s8;!ecsvc;RKlW-gj@+%0xieW&DhUm%xC$N7`su)CPVJJR3Hgs`~VuG_YUFRfTx^P=O7z) zDln%05(k)YUq)p~I>s2>$eGAgMKaWqTsQ?+Gcu>Mln1%iVYNTqGMCpp!tV27pPC23 U$I{BRF8IcO{>ul?@GgG=UmWXJ0RR91 literal 0 HcmV?d00001 diff --git a/icons/save.ico b/icons/save.ico new file mode 100644 index 0000000000000000000000000000000000000000..c8de073fa286fdfc1133bfaeb7e88ae542445e70 GIT binary patch literal 318 zcmbV{u?~PR2t;3_yPcdJ9Zmc}K8-)CceokjAaHjC2&K^Ey?1PTgVidyvI&h@3|ag} z0SY2+5i1pf%v57kForWh?vm+grw1l@Q(P~*`yM*9#q*!4oz-8Suhku?KgSRF{0rlY BKb-&o literal 0 HcmV?d00001 diff --git a/icons/start.ico b/icons/start.ico new file mode 100644 index 0000000000000000000000000000000000000000..5e7e2926bcc29bfd212262c5dd54a580e47a9a2e GIT binary patch literal 318 zcmZvWK@P(p3?C2K0)6T~F@PIpeo&bbL+_mzV{VM0j4)(LepU;onkOxc8b%AFhQWGveARRC)9>%R c-}5q5U%1GNJVhZV*~l6wmsiN}Ud?Fs2Gr<6u>b%7 literal 0 HcmV?d00001 diff --git a/icons/stop.ico b/icons/stop.ico new file mode 100644 index 0000000000000000000000000000000000000000..0c3d1c9c700834478fd836575a05de17f4b1ba85 GIT binary patch literal 318 zcmZ`!F%APE40F^OaW_sP+Sl(ngN} literal 0 HcmV?d00001 diff --git a/icons/stopall.ico b/icons/stopall.ico new file mode 100644 index 0000000000000000000000000000000000000000..b19e2ddfc665662dd7f90b4e5ba06cc9de098e65 GIT binary patch literal 318 zcmZvXK?=e!5Ji7baN{DSAQTF^^a|cCN6_U!pjR1}fjNZ7upq=gX}b|;^8fou2xNf3 zjxoaP8i13N@{=SW zA?a)#R}Ll5z8}o$$C=C2C{n9P>}8s#Sz?e@2sh^g=$&^C!$2@}9S3}$ml-)E(yHxG NzufM12fPl;+b>uHSA75g literal 0 HcmV?d00001 diff --git a/zlib.dll b/zlib.dll new file mode 100644 index 0000000000000000000000000000000000000000..288f91761fe3df60859e4051b9671f6befe2ebe0 GIT binary patch literal 165944 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%0DmT5P9A^ z1hq3-8y^-$OYAiqMM`W+w8S=vTRHi?R7CNJpvwzOO!!4QO{OV=U(wlL#Z+w!K7GTL zIG~d5zbC^gJX|=$ys@8lcHf%H8%YQD@_yc)iI~Yqs@nwOCYRWd+BGDw05Ln>(QPA4 zFC(yKmO1_$#V@PrV%TBNZI5ZQ*s~ZL;%@bRx{}vyhqaA1^TqL%p~El_{6PXO=Hd(o zX2LjTZi1Q}eSGnn^!O%5rpC*elHa15gZG`+3+ z{w;}t{X4~m_cG1jt18{8?|*wQtF{#Rr-_}~4wRHcE#j-kE?v=?cI4Sq>dSBAnwc|r zyQTTuu4S=~Es5&-c*v9{V7^V`Z<-}X+ia%It>iI0O+q)H`w>Z-AWGWj9tkH}?<&xY zPuy2FpWi`v8^#9qPf{wdfn2nbZ6`LL9a~V-- zpGpd&?_AQBkr5xkEF9nVWykt`$AaMe6brI!+j;ThKw#elqlasH2i!vg9VHJ248J9% zbBa)(TaokC-~7FdKR==`m4vS6*j^O$Jdq|Ax}4K$Ba@94Qr%-^F>iKUkM-H3C6~6# z=1iAHl(3L%ONpBq>zxxl;!ZCP%$~HB1>e@M6^9$GMdr)0uXC9}h7hJ*-tn<>nvRT4 zyYIy3BBL`=9Y4RuQ|Jq=z6y)sAE6sCLc3^0GSDPdu96|K=y|s7BF62CEwJvIZ?lam zxnc|SbEFHiV#`o%$sW89PpXk1t(q(?=K$8{DRvc^>8klfW}v7GWt%DwZC%XWa&aNk z#^uOsI2js4pBsgPSPxW#&WN2iZb;5?CEG{th2(}>i5=;00q+SFWO(YXXiS~aA(W;f zPk9BpknEas+|bI)d~cIcfI%=_He>)0?t-cdDYNg3g2Yb0%Zip?~sj*>|B_o(dK;0U-+ z8Rvx_Qak+6g6bLOU7z7Ha6mcr+xBp#bdwTq5%sGZ?J64Wg(lKtw8_*xla zBTuhMtr1zs`7iUY80TB)=$HAxB|niJBtwdl9Ta1(kfckmEr6Tx$Q4Wa7`1I_?F@}B zia{hP#^g55c6$D?Jz-|4XUa^s=gOHIJeQdN&Ttu_QC*+TQX$N}oB*b9=sL zuFp*NO!1jRw~Bkjw_sjIA-^~rPhPfz7i({hhhm_M^;xW2g zlx3`Uo9{!!N%V0@-Tb>I%t9U?^aO8Ze~`}5YM;5-gOXnCO6VEp>PfeNA{O9n?Y>=-*_#BJnxRxUz{-g*+TYkS!Ca%Y0&r#H(1G{)&A#zJ%E74MB0apmE;I7t6bGV_9CAYf@3M!4Newj&nU$#%4{w(v$ zQct8Pr3RMDQ}mP5#sunZB3CAZFsAzcao%Ie_|L|#1*bL!m;J^O80OEnDZ$mRtKAek zSo20!56?==5KT6k(5|uv;w4y^8cAL!!CKB{S>$+mkGSI?m8b*x*0cj=4A)?n1S*rm z^|o-mBV3luYOzl=2X!DH!V+<~!i@=h?d7 zM(aBegHm&14JkIiExQX2=klyDLvBb8Z?G1dITQsm&rL$%U{jih94EbRWr*kP9RlHO z;9hS*v3aSulz~6B*tBc)c~1&=oMX`hdWt*F$D3zGw91LgOFB*@#95TIo{|-z_&vh0 zvpv(2a_`K0y@}0>Y=)CgzfFmm_KqfetH*IRPf7Mh{B&Njj4%OvcoDo-Ftp`q;wco z?M3Xw-;=_Dr?5RVoyDK*bY^K23VVq8Efqk>hVzDzW{c>q6z=d4WCwza;{u7CphOye z1ZcQ6X>+WWx7l~o)a=hqSjs3I(=Z%kxT!X$SU>%FXt}s+{znthZ61M>CJ}FBzqgf< zqBwAlD`M^&&c5w?Kh9&DbhwQZ=@?&kjQ0%pK~Gq@v=2CdSA8GEb#%vi5^IxrwpG}-U5@UA==O>(&w=*0qCF+rI%jZO>Xv7MjW%^5 z54(lE!Ny+R3`O}r51Zr-3R`r8!WM0R*w#>y1l$*F>I^_)~9Rpo|%GcN7`jA-*~@s<+I7klt^v^$pZ)5iBC-&KbrPx$gC@AlkS z!sTUN<~CmOmC#@B4_)3QkGfT^V)S)5arKjaS39&D-~Jd$Ax$3Jwtic}xSoWJ<4fYR z@mcu#NXKcuQ+$E!`%n6_A2O z{c#`hzEw{w;j?d3(k&@gkmuy@cYtDV18txcw1O7Ua$()*GCE0kr}+B# z9Cr<>n_OOpjvG`LN>6SF)dP9MjW)Zm?jBE(Mo=8s58tLN)sCQp@$~&|9e+vmln1r- z4*GV2^mG|xiex}qkxHLT168$mFNi#ZTiZezw+Qu@Fh^whutGGSe0$Cx)U?--x#wn?Fgi&JNG(dl>`GmqB6AH>KvK9ik8X3De!7~ zx@R~dJ%jh|An$DUKaQ#zELgF%k!QZ-ATv;4m8Y%ZQSy?N*CxVVA*FSn!Jj-vE)Uvv zccWn!P#9RT%AdQ+x5;9qxnNeE0;9k(k+N5o*k?3{C9=(bZBD=6FxmrK%e=O(+%g|# z-LmU$PRF|4I|Q?4v>|VE`ZuEnvd0OPHK%h|Si5KygTm%?Mk#G@uNcf}PJbY3AVG$1 zXiooD)IfG(A!l>?)3FgRGQx%I&FN2x0gro>kt`$JScaULBPIT=WuYOw?Kwz4Vv_>DXpJm>GU5`}-3TrOjU7blCw z$R#>9G#oZ37x}Id%D8@Al`2*1!*-9=KSK%6w6W<~U~QUxrS~E(ryfKEU9JewwC=hx zG%Xy*LNZ?ZS{^?+mvRc?$~DJdqiG1XLDsq3b1sD(I!4Kz_x}4#r^hmLi1%lBPu!mu zW=povSDtLhj;!TX(#{rLyNJsTeX2~Kk{l`WtJQzAl8A}ypro(?i=6C2bF-ReFY}y} zWTtT^gUdZzk`0e7n2Q{SD|slaCp4Vh$C31$Wc4U-^5O2#uzAF8*`K&9k8~dNej@vN zPgeSZWUrOTXUrVpnaTik9lLs$dF$1ZCFEj48nLSU=7!@GhJ;Zh4qZ4dx4VuBK?jOt zwm9`DJ87o82`J8YB~?O-2RIet&HVScj~19FQu8@`W9@#4^ex%y&z$9n$tyBsED}_u`Gh^M>L3a!DbMU8vldV6s7r!NV?0;N!TSBeu2JHb zq-GWsy3A|Qv*L&Fnh!H$oRqNlfDWU`{8bRk36dnkcMbQ_9_(kLC}B(28CI7cO}}y_ z@yq@r#7hZUKKRL<`f82uRFc}{`pQom^3Zir@yiQ8%6$GMx?32D2E$wTdxo4nv@-=t zYp6n4=OQl|2>{QQ&L^ zcjX)`$tuzlyuA_ZDI{By&bkht&tiLEm90$%91}>MUQheKxH+m6A zwv{Bc(ELe&hHwq%LS5EoF2*AT_2`{)Gj@b;E3(ab+(bL{?MJ|tvi?AXq*^PV5W3_$ zKgRPcb!iVNsT&WlZM@8U1JGCOsyUcN$n!}wBv%e8NTd6 zXG53AvkUHE>6VUI1a)Up3MZ|-Io*}RR@(NQJla{2ULXUqqF(q;ad+DWpOyI-^#`Mr zeK)5XO|pQnKV!+CAe6^1vrEYdn$>(!+J=4rA|kVWxcT<|(txD;Lo*Pro)QtRK8{tR zdZ#`jA_aNp2;sF_d?zxI)c$zCCv)fzWknZFnw(w;SvpxiD|C?znHMV}3-?YBR`}sE zkZBbz(TQT34U2Kcg?WF*97S(-AYz4ISZFS8&{(L79|l zR9Oz*W-0K;MQkKFl#X6#YWW7zro)uFmI+vr54Qm)v%6)K)t>zKiJEKYzVj2^AE{y2 zdJ`qJ2GXdx`aK$7rf}z9r0ro0v$x`( z2^gUwcB!V_Go1qs&sF)Ji_MQ1I3hPPDAIdJa2e@n7n{v;pSW3 zDu2!)-`Z6?$z%0q1@g-LIj?u+l?mIEm|PziUfD&zJ?vld)?+oO;yN-8ZMiz|>biqG zTCmJl7GpK$V5gmhj?)3P@PY*={n7E+gi}EU<_vErD+(_;UJ@Eh*}_E>pzL(|golXv zO)QCaPzo;!YTr-f>htC^ZPI^+{!wMUokpPdN#)f_y%nC~^lR7$sS?Bvz zpZBZ!A}ad4k_{=Vb#etUf!7vzOw&6yt({9)=H9j_LHye9((3jL@tU=tENn!^);#ZN zyu3B(Kkd)Ja%C9}&lS=M3^qj;+c`+@F)O!O#u zEbfo$rH3UdVp7~8BHPPvvBS%yg1%LP7ABGR#M6~J?1O|&|EmaCVRx$_%)d{{u^#Sa zBoK=wNlYk3co~k(@bm>X@71i5(n$Gbax@3)J$7_@CY#Vl`uQuBrWmvH>(T6A7fll< z0&l~6@(=pnq<8JlFhg0=f{iL|6Bc>@7IIGdgbDbv3V&SiPGy>~NcdVpM1jZ!j9bGi z@3gnx6J~rS5X6rmE56!Q;a{9}erf0jDi!8!67Q9v`*gNi+c#dpqX1in`*;lClE82q z(n?s}D%>fWKnIiYCHRSS=PlnM@W^v1zEdd!e2tV*VPL#WBRoL~M=5cKQJe_BDd*Cr zxnVa~o^np5&vFoIL}Gzl94xM^r?rPioyMnHai(alEnjc3y|t2YCSuo6EBt2VgJQf>qR@6?_P8=jy1;CYGD{w8w$q7@8cV8tYand=?;MADz-#K4 zo^Ws}yn{d@qf{@6O5bwIGrrogwEIPRh-eV^q7xuvSan?JPmie(#)}qlFN@NrNlYHr z1#gCxl47Bh7Mwy@FAe895Z^qT)qhtq%+~X4mS9#A#$8#-v6Mm}%jzqIA0$S) zwA%Xl4kFYneRv8N`&X=F0f5H@Jg#hm`$Lj;Ow(pUZw43G3OHw##Z^*4kssDE zS=h;zG;n|_bZ|!JeMI~Dsoxsf8Ew*cd+!^Zm=%%4JOe+mKFqs}Y<1s7ZYd#I`3-ud z^MG3X@#f&*4T_L6lOShKWYATI0{!GnE9A@`9!D+1`~dP&!MMYrJVoAgsAEA+<-UTv znWV^@j(+l{4f3WP@}{+M$ihj`OD|oVl9YA~l@*ok>^oZBAyj{HStr8 z#`c!KNNdQoAw`zOs)%8HXS1{+7HRRx5t9SjBq<|)T*PFzHc8HicScMe*CyjK;!`3f z-_j<|jQEKWlO}DFk`X^SVp6M3W@N-?MogAzlbIRu*%6alwMk}1e161ahBnF0h@T%Z zxmug#XT-ZACP~_4en$M_h)JxNXzFBSCMpReWW=i*TV(q~24%8s$%wZ_JRH_0){J;Z z#N=gdl9~}eBVzJz+GJ`*{LF~SzlzD=XkhMVwilM`Cai=GzEPpgtXJqTD-|Z0s}v@i zO9iK!1xo8Q=P67vvlLD=(-lrOZ%~+OUaN4bd43q`eVO6LO!NHR zl7ejWjKX~LZwlv|A1QR1?4>inJ+0UGj}VjG=HhE#{7xGdh-Vg zJ?29SH=6e=+-%+lN)puRg}G5o^&PnNO4&vmG*>I7tO3nprIhWgIbSIs6lIoD3X_jH zT`A>;mN`i&9~GrjDIXW*D5aF4#vH1Ydqf%DBoV(L%F{~OF3N7D+$YL+mGXcn-$bdB z{JQ8~Q5Hg3HJ?+;w?z3Xr93Li$CdK9D8H+eCq#L_Ql1p0Pbp1NZdA%cqFk?(r$o70 zDSJd&tdyrkIbSJzMVY0PeWIMMlrZR!f29=BMy3;`O0uxXn-?n!k)~rNDy1A)n0*_i z2$DqkiBcwu@*|}jC(2_==@jK5rA!g!D@r+0l+P*UWKsS~DN{xHxKd6P<#&~GhA8h> z%9*0{DP^W8H!5YeDA%J@NzNDDN@X!$l#7+pCCYh9xmc95m2#;l)0A?VC?_iADp8I{ z$qjnl{|X8Q7C%CiNG|4v>lI8qfj896Ypc3>&CBG%r<$L!KUg2LIhG-LK?($B7K=;| z0p7N;_;XSMkYmzwCRQ7e7~r55(uL&}A~akD5h4SGex9RNFZ)RPw%%5KBJIf1v!Aeb zbKerQEwJLa`J7=fj18j#9j5mphTY>#HJ5d9$_C+OWP$nc2BtYhBOTHe_eb_rzD`~f z>Sf|ThQpt*KPO(1+{OY-lPK&h9};Z9+ba^Yi{Qu-I)rIuEg&4y$s$o}FAkoSXj>OJ zJV?!U%GuSwVaV%T5BwP9F@8sR{FkN3EwBYm{r`O_gg%ZME zCuu20CW7Qp&DFg{<|^VWDKtw20$suKQ_uie2XS-SeK@iRQuHvZtTVnz5r(}JAs0y) zDm+#o{j;>o{1Z%SzI`bdtR>V!O0NYjMhs5#3`xA)RQ;Wou{=V{5vKRFSD=Li7L0?m z_{A17PPY(ev5>WBB*G*2g%-lLQ#FsTlasAk&7Z+Yylq2z(6U@gb(U%wBhBTMjVjpR z4-{+@$~7B;%rugypU`*+m7|!lQVe4?2F4 zIgDabrP8|S44W9*!&(6Vy|i0rPN z*`iku*>=S@u@06OW*-yFoiovO#i#Ns3GRxYs%#;=#AilSY%kWe8(mG==w*Se^?GUT zpC8|gzT*V1OXw@%X>w)|9%Yd_mBzIqJ$@Ajs2p6GVOY+DIeEPA{E5cbip-BiuSWdnLBXAj zk>&}8%jhE+^bK?z%ZENN4_sjWTW!vX%&RPVBa5d?_0Vtz;oS{(^E)_+h8!xe|4Jo3 zCO7RM@oyMa#G!E{v8j<2xc55WEJ>{5gfGyk<;lGq+26eodP*x2bdZ4#(#oC6d?A-T z$tjWIRHqHy_z?+)O}#p9ML(1Jmcj_f^e#fmRy^1+YtLu>u9y%l!hP;LpS1bfs58xK zE$2AO1SWIy53KJs8@DfI7kubHik^@t(rK5uo;2`QuCS4hlyewiqpy_2*|Ct5n(%qA z!}Kv0X{vhnv$O~XeWNG;DPt_TMZFHJ5=^Ki#&DbDK@*=*>jIc zE@I-&5_x|uj^n3^#imb=Z`kEpX#&vSAekmf`N~Cd28U$xbviH%i01&Ek_%szpp^CnT5GqVX*_3CW+N^YEJx-aDB4MwvU5 zPvOZo&)lbchVEj>WI%URXKE68Fy|zPF(-J3nOTylS`vFaTE5IGNL}*WZ04zihh7Ts z$l{h0hUXeg$MPU!^h9Fd>D8t(E5Z#SW3Q63;1uSh`35RUY?~x@Ga6s{Nj9u30FdMM z?RE-kr1=qJs=ki2fP_*AD|{o!kje`#Jn#Cpj58qZnH1GZ?rgBs%*GV`fxNwf^(`9VFvQ~!OK3;IKanSDD=VaTb#u+8|b?``TzNH58 zWHrkTKFJVLWb`D16KZ3264D*=SO-qu2_uB% z>$G@?Na+QX6t>FesQ!|w5?e(fC96nIp`@}H_#-9tQwA_yQX=b=qcA@YopUc@cl)AT z9#9@*;YBGA7eBq1*>}hJm-Hz!d#fz;x5r7*1pc#I){2TdA|3~!r5)uCRRlwL$BkzS zo@wXN)7}dDV@_X1#$7hIjo%R)5-Q^Ea)ho&eX;vmev>v_g~{m9_=A8?}( zcmnybSD5d^I>7w^N5D1l_JAwpb~A|*=Lq>r*8_+FUyda%j;@_V5_un5Z&&uYf-qw1 zm!EMpQSLg(Er+n@X5Z$N$ze}Ih9_lm;}mW=1Z=RbV z#}ma$6rpD%*^vWl*7xKE{*JQ1aO{lcO%_Q55;qELf!l4;JmdFJl}2Y_!SnTyHA2H8 z3IehDZ?pR6TKwy5OA1^|OO}`1$&H5ap-x65*bYB#4_#DYuWhfeui`7^t8X_73j%NZ zKD5)KHwNDEeXy+~l<0xsaa#;umP3A=@*_rBnerq2j#;bZr#|R!A|bbV55X36Q{bJB z5XUW{IM_$AjS0?k+~z&w`@rrQht*$VJwq$(TQRlVis^@Oo&=X(QhEP&cD*)Uu5+c+s@{&ZFd-cWz9RmZ&<|EZaH;)AL2nO?k}E9!g;swNL7rCfx~9nu0qDELr<5>k9mMUHo09ZkJ^ic-^;hLMcx7%c6Kjb=vjCnu#ubMgDb)`^sO-& zpKU$FR3AzVSpGg0hjd*vSIdrpqXb4L&hcffztJ&E2u%$`khtw2>;2r8kIAD4`WXz* zrSjIS?Dz+-0SH9B=J6#{6I!=gUpiEI=*2(0kqoLUI53p^G1ls=JFD-wW70Vp+2c@g z{$;Po=yVUS$k@Aq6FDa%HC22_z0%VulS+Fd{a35k_azIR|1nf*Hzoh^mj;?EQ^XN?xS^>e;>0S3Sx0-Ik#@$9nS_8nTbh@r7aU4Og%4n*BOBxPG@wn)GCeE5=Rcx|;Y$s9W1Oi0WuZpW@z-u))Y9lXGk0-`r* zQW;%WB+T1ega=%&U8Eb&sgUK1RfWxZb%a`&NC$OfywP~E`53)CvOY;9qR;~Rc9f6D zUVy-CnR{d%*b};O(r1*^RI)QA)fJLp-!`iWKMGg-g6W4BZ8FI+wJ(DLp2MH!Pqq!a_PF{3(IXj$WjpFua;9swl-z-ceg&LKmy`)dE>%!^b9VtmflzA`W&vi0G{4ayt)B%VAG=5R9WGAI94GBu7&ImlFhfkRD!@i3?FsvO$|DJHqmh800p zU~<(I7FihwyhG4i&c46bVHBQyAEqd5Ik-!T)2jF5-n^r`r1~*!h{AjToH>_RTB@ft*!!CLag3B))=^f#`P@-`}u0b3nLCFND zE=e2?9e!I~gE+=D2+kEr6eLMpC-i0|iod-z`GdzbGod}sJI8__%iuie5?7~d@X$6ubtA z<<2#wH8o<;;Bl9`-F5D-sW5VD8nS9?>eh$`b*{U-TrjWPQ&d{xRqDd1dR}dnr>eB3 z>hAKnZd3$VSiZ(nRacv{(a>owEMH$*Us>la&)Hbar15|qvNWUaDZQ{gQ@qcO*{jbYbcGa>@I=2HHhc*#(C4=PoF%?~m<* z{8LAzYs=@A8S9M(V~tTqTQ29{Kr5bG=g!$!6)h-jJn_&z$h8j~0C+)w^ z@POq?(sy!=U>5>hLS^UrL<+T{r{vBjHF{*5K1gs_OWyQ>oTvD0R_*iMB7pmDG{6h-jG( zr0+&!YJdL!`Ep%J=(?<=RIXR$DkUd6Nl`R)Ulrf9{`l%CS*aaoqz!q{OZ`i`l{`uv zRH>K-TE;+#y8dpYSBTmFT7PIDy56NFepN`)CL~QxBO32|N<`Pa*p!kEX~Ra=U27!|2N9pNp7T7=u}I6ORsaPnvwNe4RtoqE~G6qkPjJY zr6pAG8=VcJIn%8qT250`IZ4~AQuQHY^+d*^HR#-Ge);k!GEknr4NjkGXJerp-oXPBqxo8 z@5U@Tk`Bz+*~TJb&!Th};_l0HkF=tNM&4K2Ev9`fqMa3fg>5t+l6y&I6)jBK;6Myc z={`a}H6G@rIHIv#f@KW?g!Es>zo)`z$MvMo+%f%dYf{#8My!L=$upQL>F_qndg(pz-jsYfrhCUqpOQAQG}f6+%r3GGMv=fL=Q z`xnYA2S3tt-NqHfHPmn^-CBN2DbbEpDQwx;^SLml{~7V%)&*Y+M_(H2G(! z@I=3Wl&(=B$Xs8c(pZFP%71o2-bJ5>CuJp*B=!Y3SW1G_N7V!xvILxfo;D#DE#N%;NhtOfKX zz6A@eFDtu#lM1Jdzsb%8{5eIji9cRZx_SNjn;X=gs8F>6X*V(=k$3cxkYd%6MPG>t zSS78w zgti)#QCIfFS*q`7y`<{aNZtgcjI+SCU?KkM!A0~G+2}4L?~PzK=CZlh;Y)wfJweAK zBVg1d8s{2HCmOHJH_`0Dw@?StUsqUL*@?{62VMxF<&KL3*Wn!I7mgDT^8d+C&A z&I)g>BIzh{5pL(z)_XnI*LgiaG}fs9vhv2NH7fMdvNAV>65MCG*E(ye8az;%>YO!o zwbC|oHkPk(Y8pp02xCquaY@?B${W_WtLmX>NV?WkR@Ia_BMB1<$L-|}bzV2IFk;l# zL2{|8+2r)rmNu4F)s(KTQH^pC-K(L#bd54|Nl7#`KvN-x+A2!KP;o|6x}C zRfr&r#T3u_(%MZ<0W0M`j9fNg|0NXt&m=EMF|en zaBj3sN)xXVa;B7`3c`qz!#1h*PaE-UGUnCPl&>wVajxD(F*TImS=jTY?mn@t&cTskZ5zq2T*!JXaJZd%?fuuri|NkJ*aGR>L9v8 z>&v18H&ntaWczX_-`WXN&W9a^RL$Bv%>tJ{cAD4{O>~d&Bnk#VLkq5{tXV{USr^2 zEc={)=3msm^Z)YNm+XJJUMv5zt@GmyzpZgc}|K;5OubTXK zJwReg;$JjI(G92_Txz3=O7{_TNp1n+-f z$2Y(AFAskEq3=BW-S7SDBj5kQ4`+?@lSsG#Ls^Iizol>m%rNe@4x=dQ%~=H zX3w+FJ^#XsFTLE}vA1*IEBm_+9DMb)*MIxQ?+zV4^5$Fr@%uj zqKii+jT(JP@|dxgjvIg3<<2XvyeehF)z?hC_PR-vr+n@D)EjP0n>ua!O*7JO&X_rC z_AQyEtJjp3SFEk9s$N&KzP7IZE_Z{++qhxlrn~Q4wtU6PRsXg9J)5^|-S&Sv|Np1s z|9@QmS##!Q=j7()&%5=u`3n{ou0Lr4|5GZuGqu|Bh32-E6f=OTxC|vfvpzxC$?`is+6&!=s0geTe zL7~q%L7~r01X&9jsi4s5W`IJc%LJWZK6nM_0)@)cn#PD z3gxK{6xN#^;3V*2a5DHPI0bwHydK;IP6zjZH-YV-tYZ&=>EIzysAosPTfh@wCTN0L zU=KJK>;PotQ%e69pF;1 z99#u%11rH88bA#g3vLACz!uN~3N0)i+<`s;d<0Acp8&1kQy>?qj2C!1W*FFx-Uhx7 z4hN5dBfyiO9qa+6!S#U`fkJn?7!zM}baoG&mVd24{d{z-(|V=mIYV zmx1HJN^m^r0WSlaz{|mJfKKpX@Cxv8@JetOcop~pm;xRECxCB(SA!?OYrs?BwO}uJ z9ccN0_5eD-$>2C}3OE(K9?S%91m}ZO!KL6dune3I)`K^Jo52}i8<-A02;K}n3TA*$ zg0sOr;4R=jFcUlkW`W1Sxu6MVgT{w3jvUYi=7Py!9+(2o15?3U!I|J~U_Lk>TnsJ* zSAmPb8qfu91aAjhz#?!5SPVV_-T^)dmV+;V>%iB+8t^#S44PmJ1ITI60t$^)5nNBw zZqVDnp-a4z^1xDI?B zjA5WU4q8AHOaxDZLqOwCqyw~pHZU0+3#Nb>;7o8Gm=8h?GM0iCunbHD>%k%5W^gFj z2HL;}!Li_@UjIB_%fIb9sp&|kvS;^y)gIA1lNJ9Fqb)QG5R&& zQqn7P{VMbnfl&i)0XKrrh&lGL;12XTVvgPdK7wB6nkT@ogH4#rn&2t)6roxR$l9PC z{g1%c!Eb^`L7AIQg1-TKz+Zqe$H?5)hdu!AAiPA-_7^>eJ&fLpJ{kQ^Fa`WAmYqn9~a))un%cpQBV*oI!#D7(;Ciaq%m3ci4TJ}7IA zuL+`W0C!02U>W*F;KR6^2-c&o23_bc z2REZH1!e6x0&GK1msM*>JNO{_Z-bA51>jNKNe6ur{q3Nvy{3SB(7QodYuy0uL%$K6 zNPcbx523$T?D2mQC~Kr!z~h+v!Bb!%C~LGE!Cv&6z$Ww)K+9ia9BaXi=uAyr zZ-Bkv4?*kSknt~w`_W)B`lX-){Uu-u`VvsqqBFr%^sV4b@TXut*a$8L9|4{C8w0LF ze~sEO-?CGEm0$55be@_kw-k zi(t~nF^->rlfmzTsrb7T%trq`&;>3BdoWJ}m!ZEKlr?lFSc!f+n2de~xDkC5=tQ3m zwxDkYXW)MvxC8wP@DZ>U%*1>=_yqcupn<<>z*FcqgG;e@g6-&6gZbz$17AnK3hYHc z6+DXm2Vf6)54Z#KYd~vHjH3!n0_#907zd_;AAp(Q4sbsB7`PPtHCP7z9IOX(!OdVj zs82i%&I+Z~8=PB8FO8mq&*cSUMp8%b|PB)!7>D1Via^U+c` z2;{FklCJfUa7!r(`CAw9C!AgKCwy1(C%n4yw=Uv-T_n5)IQ!(!8wpo<;^a@baOF?< zZskw7A~ovt3x9|Ft?w@vZNENZzBZCSDQ_vU2I7+v5nec5V!CZiq5iWlm-aEmm;>jJ zZX;7T6_J)9?Lzn{?ofV(zakq>3f(?Lor7B1$P|3uj#}Eq6i(ddDYaXv#a!CQ6inwU zwHI|hG!ETP#9ZQ%wj%1qsHJ^Off6F_Ye}7`7onDRGKCXtF&Dl;Nwb76?PUroaU*R; z)Dph58;MKYNZXP8O8Og&yHIDTa2hBPF_*kZI}-koe5Dq@iAl{O{m%tkHk zO3u7>8A=}K7&4AZ`;suF9LiNXb5!`!&ZZD+5$4j?L@oK1_9kIUI;G7`!No$<((WXk z3s6hj6ZIYZ&PV5o)U~w5Xnje0oPujfr`S)ywWMvM%9mE(t=wz1wA-i~X}eO7QV!C7 zr5>d`r438kBt7d@nir@v3m;*$UZj0XypnfmSRel#wp?^{Px4sxp!>T0$*Q zVMrciT$XktY0~MCaaTf?Ia|hDX=hT7y3c6$y3c6yTIDy|XQWJBkvh{Uc40EO4e36h z`|aR%Hdp0QkIlNibwAPdJx7(-;JO^_KAQg=@*{sbpE;^6Xg;ib)lOxuk#;TpK<7D2 z^<*nn;apy() zrj(g3V?CbCiKJsa^IEhAMB~U-b*kf7plUuEhaL+g4qfj$4&iBz)~Qa%YE{<~#~15J z$ERx`8s7qy7F}-hRBm;+E@iIEZGox*U2b0;uJj_^TB6}DR&}ex)ooUXD;%WK_H`SS zG+tN-!p$5VO`~B6&uO%7Z;hy>Jr}9;HmKP#I%`Dzltl8P>qPouw7hiuy2t9;j{4C( zQKxy1s)uMBlRL~7BXr?)8T) zs`MsZPl9@!m)i-^w3U*lsCqSHd{kYgRw6o`!dVo z6Sx|;{_lS`mj)Fj3Q;nqJ+<5Iu}F$2ib!alClx6q$~Em&tS0Wb{Ujg?uIDL*g|@>5%-AF|#>-LnUew7x6C}BT>)( zQRe{iD>EnW82nU|Q64omGe`MQBQ$DUMV|1L^&M|qQDkZ#Njj+ZM~&)bYLX^OO{I@g z2TS6n)FF~OpwtoV!ikc!1InBliBf9vJfbX2str-*36i>}+9jzW;)W{6$RBF*M7_+M zJkS19{nNc)J9FyUN-7+x+$nXSq)aJwkfhEibvVX0vM|Xz3Nke{f~MS%cd~w(|BN?E z(k3W3(~PLe_=EV8joZjOY(!1QNz}}U8b4990O}k}%{(YI zRbMhS>3fu#{L0i}lDekM!_oF+V-YG|Y79iRU21GiJy%MN)nw-698KD^Y#dC@qR1T0 z95qea5;eXg&y~x@wbYD>JPB@&{E$3SV+^VtQ)=={&FRc>1tjxKYU~&&DIaS57b4+aP#tfC@nHndCO3pXbxR{!WQR8K5c1DevsP;;YbE(-EHNK-lrLGFp zEQrjFNFGT4rp7Cz$IH~=k~*fwv*cGc&!A?c)L4qDGfGW*nPbVyfv9D9C-V>T_7I7S8oN_h z8ftzmYb)ew>z`^eAEZ_nsQDCC-(;PEn3H;?<}6fwQ1e0Z1UQ-F5I0eXQ8wS9M#}B; zOqo!>?ejp*0cW7RWpg(wUh4Wz`XcF>vUMDyC#~wIp7^8O21(+l)Z{%XnR$$)uTkc) zlJhtl+3-9Z@?mJ1Y<*I+gAT^-K8Z^Yz(9|E12Ja-D0#lzn}L9Y4L*R9$hwID$V#JJ_ko*6U{Uw7~7x zniK@Xs}x6mU7&eAw4lSTPkRb1!_xDI+`3qF+va2Oz6W77%;}yD!>(%Hx-(IDZSOvM ztr?G2KPG8R^#y(J%990chkKcvIW&Ib`r}tS?mAJJQ7GR}b+OXa{9283rjOP07R`>d z&~XZyu(!MSDWQ45k=3?$W++d)Emv%OJ$-=o&DJFg@}~7HIHJ6(>0~CWs<37IMU|QC zkF<^}_o$^Aq-!-6X@<--7lx~D2=hyo^Ym?Q8xtDp;~i#HL-$`Wn&$KR`FiEBS-a#6 zPqnGecR$nd?YY{6{w|M?>OLtfSvGs|u`dnX@A^2J*Xg~PePg-5Q`P4Q-ua`hj49y7 z(u#N7rx#V(?rIoOy}m{me&*WF&uzDEggg!&`lU94ZNE5he`TRpU5vS+-NWt*tf@{N z{^R8f;lFJ{q@_n_n)k_u{Yh3J85afPnL8P%-Rlu>#Chsd+AM2;2U##ulJH7JY z;XT#oy9f_WoTqtsFyHgU(fwh^OO2BYpC37Na+AjWBMO_Y=fzqNzxm`q-1V(`X15iS z_3y;!s{1}j;rp!evS>t5b;lR5Q3LLKlMcdP@ zUJc_K0{678j9|o<1lun^$gn#^O zKlKy&Ydj7QR17)fnf<-GjB&NHU|B}(pzgW#lMa^oZ8m%D`)tdOkm()PhRf$Dt8Ec; zw4N>qREh57PE(pc;^g|y35EA_2j)$u&%ECu>&11=YPV#?(Pf7eti8TeSh| z4K^?NHCC><#W`m)it6WH%^&{wdjZ|(^@$Tp%Z^{7uRY|td&gl@B~In>waV3(`@7e> zoeZos8JrMssBnb$8F|B?$?FD24zMawKfGe zQ1NV-->|m0ISaEr#-C5Kbyi(kd%>}&?Ap?n)=%ZNZ4We@3-*ml%q>`Ho}6*jD1Ffw z-LetdCbh0NqS|Jzv2FDpSDib!b8~@JbwRpOT1v9g#B{^?2066Nw?CO5+*HoVnLIhJ z*(faRdxO}vDnrZT@^;gd^$+wimmAyXXM4xxlS?&Qe+Zxj3`S@f9gr@%1@1JCKVRq1 z)BP_h#fQz4bu+{A)fVnGb-4IM&$Y6@ed6b!IRE?a;>8w69ZWf~hHBj{2k0ukE_NMx zXobVrsCxf+Mr)$iw>Qm?PL_TBHaoTQrZB6#;uB|Sa*;~vrkQTVdDi~fdq$=w^E>3E z_rCb4Ky7h(uD0{!)~Q-yZT6SNwc*RP%3_D=dc2W0v3+tXD(qB}ZCuUpYR(4bX7iP2 z3uwDiQVff`Ew!CCsmLQJy(MmFZEcuG4`*||sfiqoE#`)O?;6oWy0r9Tu8icY)jqk) zuDKSh(LdXE*S)Rv%)0Ed)|+Xy?;aPcA2aK}@+#ikn)S8i zYH?^z(pQiCjUu(o{JGqey$4N=2EEc#v1Hm0=n)ggP&wQfJ%aeQUPXp*v}Me;Cr-9(pjGzijmJsjhL2ztY9I(Q$m@ zCzti+)w8OMDttOEU12`9D2<)kQq;4uHcR>0o5U&m%lwB&q&kFIWVudn;pp;mRMbpD z-0-;w_?T&B)CaxtV7`6xkc7CelL|up@3eU&p0!eQeKCXU@IEhBt?*^0?lxA1cxm)k z)3v1kTZNR#MO39IOf0dNzq3b8Da{lZUiVO6el?umn(pts8*74SY4VsOu<23v} zc2jd*Tcg^+JfnkfxTkv54l|?S7n{2Gh}+(?e|N7Q`_wHAH+w(tR@SiB;B7&0*9)&L zb-$;-@3h-2PbbwWM!U0zK+lB!t#iW4Lj6%q30>~lkK$GjT4lX*%|-sP59&O@3p=rT z%0kgtUa7#}MP4|e%M?!JoGn(I({+||IR@;MlkSW|H`mjjgx+A@mD6FZei&++y(*{A zH%4pUi<8XFoJRSW%<7xfyZ6RsW4$+~z4{%VIiP3#@qzYUU;0n$AnMmD#!xq^khSz`MMm}EsDI#oPLFlAsaUt?! z89|oZH^HiN5BP}n!hHkw4)h*#>akZ<^=|(MC;a@5EA$Rn5qvMuxV+k-%Qij7(LG&9 z#!pWj@x`~^xm?X?^qQj{PP^`=k4oQk#{R%d<>Bu}+YCEpI^WKYdD%8*LPwhc&5lF5 zFJCs)CFqq~pge1wsCc@&+O4DGmzREUE&78u_I~HtE;kcP#o zJ14MHpH5cz+~3n-cpefoht__3#0)HasHB({^Rc-T9r@ zy0!mVBwRejvJC{B~OXvdlvb*L%Ia`*5Vyof?hM>ehgqTSsbItLEjK-&EY` zQ^~rLbuA>S`NrgKrq{K+8CU#81G*8S+M@}_W3tXt#8pO-E&}6 zdFI(wsHuw=Ug_XteJXYpL&Fzd?slW}s`)YbD>i8dQscgauO2*P;F_-e9WT2)ikmoy^~Vh#t}t~&2z53*+jQovO;C* z$mLTSHCF~NySnPx)}*Dk-w#?{jYdK7)C$vMrvn zcF!0$eW$JX=&m#UKV-cb%G>tG*V#L^ZYbGa8FQxKQCH=|CD}GdmRz2HRO9yLe12ZX zLp~iH^TzovI~dfvx@f9{-f`u3u7!*(smBg4tuIc#XLO=&ipR<4>~!*|0?j|LxNzA8 zwLe}QDA0A^;IoSPvfk>>)X+Z1xYb)*`yD>J)LC_o-@Ox&a*O1u3s2q}aknPqH7l;+ z-gs@_37PI)T|UuwwRFzZ{xt2wz9AP%XEp54J|8$#HF~;P;Gte>rd~~Nc zqtake-z({kWZlNDzT(lWE=4(Qu;_}AmJjcw)EtZLJK-{P=G zIHB|A3F18_M^<-d+<3ojNyyb1ojdNiw69TRZb^#QY31utSyPU7F>0PznA0X`&~sYw zsO;Lkq{Lt?i}G2a4soZqchoFDACwo<|Gc8NQ&wT^+w@4g)!%P?NpuN#VQ5u1epKIM zTK#O4j+|>fxO2y14Wn)cZ_k@s9cZ08^ZLNHo)K#m-kb3QR&PpPEVf8}K4EQtH!r)x z43!TW4SPoQIDYlj?q>~azAY&nY#-QVvzvTW-)BV*FQ?U8I9;SCeY`#H(Jn)- z=aX-A3F4fJ?zl9*+-h=Du1lk*&3CuaebQUr#VlTytKVv+|G{Ry;?};RV$+_Fd)dw$ zxA8#ry=9jUdb0ROCO)5X?cunCi7vx$c766etQX%$&3;kcqsPn(^H;8ns>*oYFYn`+ z)l^4~$ct?=2j@DJmdbs-zAL~s#Ar&|`*`aC&KuG)C%=AW@=&R3=qRtm>bEPe z`8;}hq<-qym1<>Hj|&oN?P)>9y^1TJFd7!G8t+T@{&=gmVn)cO6T45S)SQw#$%#0; z-&IXjZ`0`L?%(@g*`>2^*3sC;^BphOYOmQ-vTuOBPFJqUj9p371J2cG%O!Yy%e@-C z`C`na-miAL4^J~_&9UD9wPK3SFy$AcX8107b!CN_-JKTO4}N3fEk3e)thKGnGjlz8 zYtz%m=RO`i!#R|srl8a`f-h0sLo#&*l6CXRKi^jd`r93~(&RbOV$CYL0uI5-J za&70{nrwDD;?R;Avv0k)bSCmv_lX_ywly~O==^40{GO53!R4Luwc_@AZYw)xeJ)Wh z>08d0ha2@w)j3(5h%rVhv-JZOmQAY@4XTeE&dfE#u??D^Y#vE4(KhrfT zq;1t>-(8j&e(QH=-CoR$zf-vL-p$87He9b=taij{U}E0v6ORhbRaT$up4$DWwqM9W z;;ov90e`S2ZX6Zqw7Ys)8}eHx``l6qY{TFLY1oSD%jwOZ_iC>n!K#?5iAB z`>CVQcZI6vTYiM`1*c%`DL1`ro3;l|ixwy}T^p%bHT=5fg0H(e6ogt9?D;e#KRxVr z(ZyT)ia**gYr-B3Yw(P<=Eoc zK6kq}=+&7!F26DR&F3m7|55qg6LIyg}k@vs=ik3 z`_>M$+`e;EGV)$_Tx+*Tb;tD{a<7AemCL@p_i>+`xOu%;M~Eeo6ib^?m~nd#v$16rxyG{XM&&@+xCcZN{=m z^|{?Q`;{Gh=KI=gddQ9~^5JVcY*ACrd8)+`N2>&4$<4jf`jaE(-!Dw)JS%TtuEr5V z?wac>H$S{tqB;9c>rwUF4ZRlLxL)i&?RyN5pXtV@oVSE7IU-7&LCkq6@%4UP_v zTdeBQ{JmpHRDd%7l8&6Z=PLhG$ zXy#q%rT^YBc=#w$MBMG%Yat_QZ+YCYX;2Tas^P!PDb97RFUmMOJpbxkdcpU{Cr-RJ zx^%p3sq3M&bkoB-b|0_gC|#~rUh7ux-ruA)@Z_O@gu!RLM-)yDGL#<>IdI)!^%ASo z8Y{PXC^ZT~t&H_4rrVci>OIKLw>K`y ziSvHd(zv_jP35E0^~t7nXY>6hPtN_r&OY;LWN1ad8Bt$@tr?-Y#sfTB)m796=^Amv z8|*o6&P`Uye;(yFtu54_UNqp%`|XUfBTI}@r>|DYVt2_b{;ZX<;&`8&`glQpD>vp% z{P&O+hZ5VfhI1y=4-mYJcUF4rlW14wY`*BWo6)&-dQ0V2XcgJ)Hfc#d$*nEpW}VgS zRn+FxAuW4Vrxj^eJS%8w!>Sn$5ubdP#WlKKI$ku{ZAVs^-r^PFTd8SU8|tgmUCvkJ z=sauubp7q8^0m=E)t;g8&E0L>3hF01r)27D=}zdVXVTS_8&xMXvEA(dH8guR769DK z)%uLCL!~nRLc*$=wQ0H(pB%t=S2Jd z_x+-D(Vif9O+v8daT2WL-GFgQC^9yj7QCHzJ2>zcJgLr>g!XE6tmgchL&Y78|CdOtW3uEy$y2($Kh;bdRvwT0L{; z9vYm)seKL2w}@y)caNphbhDBfuFG_?ltp1tSHuu)D>~-&I({ARf6hA&{ zv|`T|Mt$f4EC5^zjemY4%3l=o>j@h^xt>X8~@(&`EMQ?=9KNE zr=$k<&&)DwI-6VgwLbIXtv40Ct1Vw=CFO(`Z_M}jnxCmA+MB|iJIKiNprwl5s~!XF znJSF97>B6F!(&1#-zL~6b9^V~$CZDo9(}#Bx#LGH0NkriS#cmucf^quChA+WqAsKq z*)CBqnQY&U8>Yh06QAj)rL`c?Iekc?TaLoG_)n!yKIQY~anJTlH))%4NGp5mA9`tb z9=g#Czc@2Is(pMmUWs?Dacp$%;_@kR{H$v8^*$9wRpu*}c4DU$jqO>~lB%3lTRA21 z&9mYDW&6V%QX{6jW?ArbIV~n?DmkjAZXr5i|AA@Q@lC6W9d_+ZH4HehbU@$N<;C}& zR<77q`nBGtwz<`$b8+Jd&FsoAW=oUB;?(@as7ljg!R2}%hBVvrCw+}exZ@vMa5mAS z?S-qF)q4l-j6${CyluLfFPDldSZhtclG`{mxv~%|g_NoYdHa%yDr$S~D3_N#Ra`MU zU&&d2fmWw6g&nW)S7~loa7$xcYol7XkAA8(Yh62Jn0HY394<66+Y!~h>EiI7+v9rl z@ao>*&_aD*x98rQ4fZyabqy|ft84lCLZ|oX-*xiLc5BBtrRoVhI(Pm?H_f(#dm#`lOi>q<^2Q3~UHmyyx=gUzGAGiq?lgyOkR!)% zKbb;bfAbLYM(7ikj@(_-(1)x0+sgQ6Wh2-OKci-hi2h z`wcu^-?RT0FZ+I?4%5u18MO^MG~@K(w-+{9SZ&`QwP{7^vY-i zqZwnI2gY`J6+Qjt=3$9T?#!G!@`&>sO;!EbSABZLC&liZIi$zh1jVdNv(8=m8df~` zV0fb4qDbzi`iM`fABPq!3=4^SHYtcPRzCO**V5;}Tvgw2J+b$|y#Zd2PmS^4U0vnp zcj7@nZ-wK5_kveARF@k&>TT;X(zWO45vkMTo$GzSj5bm$ck(#8W>os!UG`@-r4Ltr zd0?2$=y!JWO;6cgX4=_woDeg_v3bDIWy`y}y$W&}$C3|ppI$5)fAp4`>xa_iV|jlR zxy)w_R-0^f5=GOe{~DDQ`*)~ z@!Qf1B272ODRtV+C>pWp&9ylzmRQo4kDRKqQnPW&s;kR_mnLm}wrt4z+e;K3=Py2I zQjomKZ}I#IS|^iw9BiISDlKcr#iv=YD0@9^>O1{s>UKvUg|1 z$K0ngXCLUVJ92;IKHYu$&-KZEHP2u->#4=g>EmYXIx4o!`q2N(Hr~)TJ7$0VV|&Sl ztp#UdDi14neRRYoyX5Hn%S-Yv-_|(PF^`|;*wN?UGXHT!)xCp`>p4so^yZ0L6SQC7;$GdcO&urO6|MW>av-6OBAEq_TD!mYR zK70T4Xw{*YuSA-y-#<6XT*p99yz}ViMFy1_-LAIu-5#@Q$iZ{Q`&1K#U(8}25E-nz zFsTDSupoaCtbZ4MhU&v^8v54fM35@Qmk1W~t{zm5+S3~w)+S9RQ zu1cfwX|I$iSy9)Ujk+9d%PE}bq}L$0R`zH?aAMNES>+a5r{f$#%QZW0kI4%<@2z;g ze{ErwlU-!`+b=i1uYM8Wk~qH3%24ZA-%&@DZ2Ijy*m}-LWATo8w-0u+4y>L#@cPWu zwGlnr_-5}Fk~ggmNVO0z?!R`z^TT#tZWhW)D8lKq?E?nZaxl7=) zzESeiUOE(Abh4`iC@|=x@odixo4wG?r68~Ht$-} z`{XWL9HVd5s;@ZT=0mZl@77+Adzx+>H`8|6z3Kxj&x4nqPdvgO_wd@3;Vy{>zd!4G z(}&+HtZtFL+6Ct0M^P(R&hPg;qiXe-k9qT(#!uxhBBTF4}2VegxR~lj)u6RbdG&f!Xji4T)rnq@e6FpWT7TqencB*+2?dX>f@t=&l~@2++`xE3 z_Z`2g_pOiKn?f=aRj>eXQtnjE{=*R*JykW=?$M(+?Yh$c`_WkobvmAJj9sH$dwIaV zl0Dq6I`&DsW|-8R3z+VefOo5|=6<^rb8)l#&R4x#4bp~x-EW;UOlL~Pj8QL?ue@5~ zd&kafh2IC;mX8+kV{F&-)ji#G>*S%2=N_vM+qS&xOD&&mPQfaI&0|VZ`f_fk zt=VWfcT{Jc)(!PR@?wDOOYZS#~%JZHTn=UbAg-o}TVEROn0qcIT+1N5^+b<@fQMb-~m zcjK6&n&P~dd4;J=8tc`J3S^ab$7+VoSv~7R}un_d9SNi zQhtT*FF9;1Xn3zu5f2w&r%^glE8#0Cs#3Kw%@}`_xuWFjDfd`3ybq}@F_)>6Xh}3m zZ44Dnj;M)BUXG?nsmT{l@QNPHWzQhsi)sO3amDxMco9Rz(Lqy|_HoS^>z z|9D$Ubu7?D{5uf83Dg+gsWM@I2Jx#yjVB~kdqckyQXb3)L!fVhe-xl9-ds@a2K_q3 zk7w6a695nTD#YIj>JTsh`ooZJ%7J;n7W(_JC+Wfa460qBUn8x5 zJUOds4gGa#{o{!-)&9`uLz46^0glkWhJR9@c=JY84;@2Zj;en=X{c%m`%BXL#}inp zeW3pXl9bm1Fbw)9@K5R=Z}6$&EjzVM()ynRy%733Y5j*oZw~!&NYb8GfKkwYf`3x~ zq|J0f{!dHmKL&c#gW5T1{gW~^fqpk6ai0u^Lf;Jkr2Z!Xl(SlzwEky7&xih&wEjb( zN4r)#0!iA_GB6VQckoa0gD1vSyR@tSIOwfle??mVfzbDbJ{OY2w+IY}{ssJ#^2D2k zs_3_B8PfWn3%wZndTIURDK1qD=u03;ds+pYq5meW|G(@1rw~4=e=lGR_uC+8APLw& z-w1nB{}X@#^r_PNp9wt|`fJkq4}!iQ^m&k^z7_)q=wHDsw@T|j5qc5ycct|o0sUa;Pe5vcm0&dVU;m;1 zpGNp3z22Y~-0y&t1M`3_^bcT9(lZfsg?{aC{r`rv{)1uPANGeJNqUz6N9f;L7_`uB&MK5)Mml9bm1Fbw*q@K5T0GUyKdW@-J;fnEsxZE5|7LvId!5u_$q0Y*Xp z8U9KA|6Tt-FRg!HxG{nIY)Ilh84QK~5&V<-p9Bn{-yp63SSEcnI2seG<{s1J2ZxI*{eJlKv@|5-eOlkeg`hSD8{v+YW0`5;j zb_A<{GxXmv649aQk}i#>BGqWh_};e?O&wzy4VnVJTrN-3qRG+VgQl${N9&>_C)Y_y zhi0gRcj=XsXkC@$6!nyBY2B6NY_ydWXa-7h@|~4>(TtSjjCGaNXx)_LRP~kS(QJ@K zZ6$g5p9lY4iGKqfB^BadSx?EE`1c|oAcB8|&Poj8pRTKGoO&`#4HkhLbsfe+yS)4I|xOGvW(l6=bYC(D;UO&_s#p&8(>EB?CS z&k%n``0I{88~jPW->Rsjq>Mua2X<_c9BR^|!*7TBZ->S|9Gd@dX#Iyr$N%ua$XG{L zw^OIiIP`GnOOGzn!{E20>u*Q5e>e>P;V}9SkM95B@#Fiws%mN-I;g9Y>LgW0s)ZD- zw)D`E9=g(_lk_0v*8U)6*#3}}r%cMqRVIHc-al!7EABsO|DpK1{ZsMNB-x*Q^7y)^p3&^~z8&C%NKnwH$azF>Dfv$l3=>bj99VmcKKphwY70?A}Ajaju z2~>bVU>^tr&wvrw0zAPTAOOW+HuwUt+b+IeuZph@sM5+nZ?GNsfcroftN~-eb-)Dq zAR4>|9l%mB0$c(Ez#kw4JOSOnCg1_;01q4o3E&e@1`B~bI0wwYZV&*Pfj&qB5S##Wz*o=-Ip`#(Mq3NH7Sa{c z74in;4M-Lw3-S=;A;=iW7|1t}Zy?nn)ghNbE`uBiITErQvK(?C*`H+0bBFG}hS&*|JKSO?oBv)4z$VHHgAcsQ^hdd8? z9!K*&dsk0851c7fagxdGB0(jBrIvKrC~(hBk@ z;J+CF_Mb`!j>6qfI^)-ypIqGhq_d5Od*e1*$w{zi)>=lJ?Zjk-M zBD}+>ouGI>hVf*ldGBy+QYG134Bw=|zU0x^s|f!6ykot8Y#bF$wrIi@=6=|uDk3m8 z$k;Cu#YZ-K3JAuAPZ9W!CFLVcTXbY(IJS&3j=~P@#=b$=RUSzqJG1|u4%uz$-_sE+ z-H*#hvL97!JhrleUuuu87;Hp_giuygau71Un-D48uS=Rl$(Cv{vEJBO3Y&J3-P-YO z3TYU=k$z;qEs~WO>^~(*yUfl9Ig$nu?H}duO)O=05`X6A#*&T8A_7Svn@0wiOVUEp zAafUn?dYSu!z|iIA+z(tj%6fKvWR8ozxqL)`1$_`Pv(a*{Ta^>bCUT0?`UHmZ09aX z2dPpt1mhrV>g6AeO7JHZ)1`Z=$?BPGz83Bu8x-mHGfcF9Y)fSv6P$oA(@5P*L&4lH zP?l_(EaYfkQXJvlvA+0POoS|6N%N7$O2U?zOG{R26YYaq#X*`Q-4Xtz!@904UUMJ9uN@h8;l*uWWB@(CMbm;`=F8Z zghyfDEbLEbJOfP=1wrc1f&Bb09>yN>=&!c{o=psH~Yel|JvSM=U?0Bb^6!#`}F>`{VM%m?Q4EZ-?uKm+P6=CT33AI z7ql;*-^*`%cgmi)sWOt&mX+i0@q9J>HJsVMrQ?aoulB!}^9R#^ZJ%ZFukD90eziaI zTl^brf3?I z`7nhY6W9S4;0Z!N0!RaYfI?6K>cJE64ruV?X=Z>6Mgt!Z4d#OkkPFU%dZ5ggr|E;i zzy`Pg9}o>vz$WkqI0u?Q8&DO<(~LkrzyWq(Gzb8(U@6!F^1&r=2Q-7%;2TgD%F}c~ zKQIJL0MTGM*aC9F8Bhh9K^ssN$kwnNx*Jouwy_%8x`p{Jxm$`9sw9e%*f7$v`(t!e zE>YWtVZcPwQF6!rokKD1a*G^={XT<-V4F=^Jn`=;*}c>)INaYhMiLuTw1p(@p^@R( z?bOdHI0B_`R$?+8Lx_l2w;&XcpChioGzG^PJAa?)fr0+fE*K_~jZ&Y`hT&T3_oKKj zk-eeY=@sEex?N>tv^~msdbGdH?;=tTuCad7Lc+?*FfvXZisBCT^$v5z_(gJEwpMWR z_nzVZC-VpLjuCK*om&%rx3rD$bBzj)7#cYpY4nri{AWDZkj60o*x&uod?|O{(XrE` z$fld_v`tjWM8rgf`44r$cl6uoQ9USkSCWTe5q{1ARI3;vt0j!0N$sGv6@Ju#TcoUF zXeE-CG&E8YG-~&T;<$()Nn`OFHq#f|ZzJaj5ye5H?ILmft#u5sB|C^pTV83qx|5bX zHcE9PyX{7xnahl5XBFMTVyFf~J5SVT1mnHKuzUFhqC=pR?i|rc8rf5nHxfj~-^IlDZ0_vW)|*R z+sEM;@gt57Xh%p-u-{P9xG+7V1(AH(l3KAMAvq5lMby!f+`EwKqvb2QhWTSxbOXh) zv0>vP$iSA?>Cds+(rDQVW2XB@$74%k$tK7col9B{3jfrfOA}q8j+hRMm=PQu89{cs zb&ZV&bDRmzCQRN~9r$}$~Hy>=794{%Or3%iHU7RI-j0zGVirc$` zKA7ZyvL)vVm|U0i{jvU3WxC7Rhhg4D%5;ci*o)awmBfWB8Pe0t6`Y2S@{aluzT_m> z?q~bu;dqdQM!Pgro=m^7#zn)rBK8Kr`LfMMo>nsi=S^+=W8uRKxb(uB#sJLM`(sT5 zllfAx8jqWK;gKt{nZi9h>Xamg`A`Jn26LznI>HW*m6DGIdd-FV0E80=H%Omx65P*2 zn0v6F!Atme#o7Q#(MZT4SR=5({uGc&2rmlZX}^M;5C0@Ro!}mBU7QdH%BrC}kLR*H1~(?K>zaRH^EvKVPQfpAKY)^jiet4||67ZE;q zc?tS*==Z^nxPjgVx6HlZ&IUx?fx9N~5N@Bsyp@XGn3y#q?A!4B;Ga_T)83^N;edZU zRv~^VNsxbc(2>X0NbhP$a%3UDNl5oW z&ojmFs;}T#B1_5g@47h+PAQZ%d#b7Qn6~Tj=N54Y3s1|*bD2uTDXsqh4tF?Sa07cx%1o?C$^qADfFfp z)9AQkMDAJ-#NDp}wEnoKu?zPT$UXTjxc8xt`#;+ycdt#c%H9=sK!#&geHQM3`Qk2+ z1MUdLQ$J2Dn`q84G5-<>ue z_l;a|UnT|X=?icVD;f8Q7`We+5Ekqc=N%O+89`#7cK>J=lO~l@v6$h5aodmOJLV77 z55*9Sg#2i}(Z28|`TgNZx&}v+O3I;BLtY$_SzwSOQ<7m9b836t4~+{J`@&;nRxVh% z!G-aMHku4KrD0<)dMv!P*Nh|=YpDT-A9g?2fqoidOojCVgI!$#{7Q{fWvmD1^UNMMNv(S^~Nd@Eii#vM1sOM%-=g2Ye^C%2Kh2M z^@qvSIGFhblNC6kh>Y@&fb@?HAit6cLSRAwxk^KiXndt&DkbqP)r81^08%_qP=1M! zgc^mBMl1;@YP!T91aVKs=n^3rD z2@V$NB+qupPpghm@+G>DYdtfG$pCQuT4p27>`!cPA!@4$qdFtpp9X;Ocp?8OQldI zNTzf@6ho=`5gBL^GvuqCi6aJ}XmNjO{%2DUjK;7K?-CjF!w(7eFZ5Es!^0x+eN-}M zBa<*H7Rv0Gh<_SM-8e;JVl`}LY_xYkuz#4J#5B?^^4G|I8vL}!zSBQ43j5LiGk@qw zn9}(%65@=8$|U|Iim6n7i2A2WX4|f~e)wo-#`+6)e_`a}9TO8587;{RnVq)_fw-o| zxg_mmhJg}8e~{D{>5Z@_y@E=bq<p(1iJ{30boK;Yg;VvLe96 zh?FD@?8z+w$<=B6&t-JU*zCVQ|L049%-`A{M6VCX2#TN}m5PvLc&-G_I}p#IRlx4ko*j@bN~GB`4lUS>-r+Z8>>G)tzX#Ui0JU_mVo?-9Ra}?V{wo%zIym$|f;M zBKN@ksU3AYj4A6s{m%PF8QDl>!N2L=R_gu;++W?^w7nbc;r5p8lm0&Z#BCBEW6;sY z)J>@~rcJF2sbl`x{f(OXdmr&l`9xXD{^aC;Xdru1^3ReA$oOxM|6xS`-*)cpQm1r?wYRDl|B2iyZqpanbwFF_ml0KNdl zQtZ793;=O&4A~FR0T_lS;xM_tZx> z+Z5$>J!v;kYMB&9g+-)X`-mi0-}WI;$A3%Ue^LUnHYJlXzcRf{ z{+;>X+5b=7%fgXKng9QjQhlqO_P>KHjpW=g7#c;$e!>6DT7z>UC2xQmjwq2s**I^KaHau}fDzKI3=%G}$*PYmwMkZ{O;dBN~2 zdE6843z2;@eQ3tGQ$u#y^oEM;Dj5!U(Gp)|KRe>u7Lq470!v}uspmLJjNv;TzqW{x z)BxFCkdz^bk(8RZq^!xFgk;aVV3?VK;ZTt@5+BBBRgn@M@fj;g!)%nQ9MVH}lO?$( zsUbUWQe{EyT-Oh#{o$K|R$+}anB)ud~Z)vO~y(AT*U=oe@ zib(27nUNYH`6O*}yu?mc+CN((4ez_4!{8wY(gw)>+|(X?KkHjfv57eNfA2ttC$+tp zHHLkJ{gVBjt!$}n+1HX|ImFV*(#LX|AFd7&f%dwVIELE&kg&l>8q9##`=$YuHs7>@i z^hHDyD~eUc8sbbb&4%LabV>I&*h%9e4@6E4<~xZNitrA&3p`z3@H-#7%l0b2Mu; zdmTHSy@j2{-pSs>E@hu#Utm|TuW=r79$3AyT3{W@d&q0!@%Ur-H6))=)HB?Hldcbn_H^s#==JIdz>HwmjmHKIGBdw-KZZE?ZggZC$WpzO*}#DN#aYQ;#1>jbGmW*a#F3gT4!6Y;cet)@v?cjyaHY^ z&x>EmHx>j5q6LM5GQnqorZ84mE@X?=i!O*%#T~`^;_irNpqPoUoyFtDlf{1GP;s<) zv3Qj@O}tgSQ@mH4FMcL|N8(#ez0*Xa#|Rb(HVASAOGLCRs&2P1w=pwW_G~wH4ZE40 zY`N01oJ%XE)_HGPJ`#K&g<46ie?B7Re2-GskR`5{RR4KJ@5bn7xxg~jayU1W+t1p` zy46~pXUOC67W0~Uz4>DP6uyC=w}2<`76b^w1Tlg-L6aa_XhPD4-bdQA6@3_eDm|7y zpT3S>OK+psvA(ewY!CKA_Ez?3b}RcWTgB4Ma+sy3WvJyGP6}ruX9wp#=Y-WqtDanE zuDtb1>%-RByxY81UQd2Mel}lOFh_7ma8uA#xLSBhNEf{p4HwfeyGub_3G@&6UZU8t zKj${5x0Q$0RI6aCtycL~#a8F6RJh%__FQl7QEmmdnd@jBXZ_0h22Y1?#P7{d;&0)f z<~Q+Q@-+m`$V-NxNKh{LC>SJU3VEoXVqt~wp-@GnCGrr3igHB9MCIrO??nu;P&`H) zAwEU&Yb-~qgXT&1qtBvmq~E4HFlI4UGtwAW73_8TkCJu zFL@t%5BU}XuD}cBa9*H=)SVXkib6zLqDr(NEAc3Cu((M~qs!qLNUYz}2hxYr!|0jx zWAxATLyYr`jm%tT9rG1anPtr4vL>-2Su0tak(LTp1525`nZ1jB05$V~ZELyMa=T@| zWv%5qOAU?#X9}l{v)wA!s?e$mwWGq-<0f(Ua*eHRtyfv^wXU%KY%Ryr;dSNpO#MzB}KO@52w_NAjoh=kVwA7xP#1*Yh{?ckuV{bNNU3Mf_9zi~KA6 zD*hdQBfpv7%5UR;;>!zE1eyXpK{tW1pr628z!LBUHUfLWXo0I>qO_&P3gQKG1&ajB z1#1MG1etD2f#&i57}dMVUCu9Tc4sokI(`BWgtJ{2=;ia(LIV+Av+2zRc~+M32{<*N-RWP2|P$=J1kv%Xz7i z^Wzn?Y!*M5Kbx;Fa1=zL4?You2v-R=3a<%kg}p@rQIu!~O8B_wx~N|CT%;==j5F^p z@j>x5oDXSUa(Mq8_r>V?bQ3y*ZiR9@Mn6w~K>toxVhm%9W_aQ%aDY+5C}&hN9-;ic zGPIaR%)U$urhsXW-no_e2lEiKnyJWo#p=TD&9-B2VBf@bSl*IlIn*-3a<1iaoCz~5 zcUXS2oNJY5RcX~~Wy4*-P33N}Cb>w#*~vsO5^YRLWP;QV6^%sOa1%`s%@bvd&Wb9~ zKigkvm_M!}e~1gjmEvmg9q~QVb8!)oBd1{G#e(il4?^#a!WncXeKtLbov)&YosgOe=xaD60upGpsgR?Xo&-b;9Zr`m7vR zpF4sZg{$%^?p9oxPjJh)ceu~EYStaCyINaV53!zL?QI=yJrmchO6vyeX6w%A&kUUJ zoq6NXe~WmRc~5!Yc}h47cM)6>C<>i~lY|MVp$y>;;c?-2p)$^qiK1lDa#6ZytLVJw zvgih`s`|Kk_7aNu#bM&3;$rbMmSoc^8?9S|NY%?~S&0{;VUD#gi0CpHVmYu*(VlQT|#Mod9 z&R+Z2huJ6CSJ{u)&)M%#F3OfVmTs0|xQ^vnwqeY47uWueR_eGOSa5B*u3Ud^9CrzK zGj~7tB=;KkC9XN0(O$XMR|G6!ws48a06n)tTu;)uT8>;oX+!9abWdE*Hqo=_yXpJr z`DkgSXlqyKH&HJS=uhcyaIRHhXfyN}hKxQ83x*gycq$`^v5%3D7WkH-$(+cX$_!<0 zW)?F~GoLWuGrur9;B4&6N@8WP3RovuJ&=!b_8dzM>uJ_=aOUsGGvF!mb@>DNY`!b5 zyFvT}ehPm#KaX#Yv)2*9B|#;wir)oF!oI>7;TGXp;SKZ}J=8>+Xba9s#iA8rT9%yT zey2UliACN$aY9~98T}Z87<7h!VZ*RvG~t}rCisGy)ev?T8VOB>gM@6MKxl`Wb`yFE z{cs+P70wo>2$u`j31wp;TB#gH-gsw@S;4GiRxxXscW^#xVzw}!F<&y-Zk8cUI- z%FzUO{ zD_W%-EgkdP&S)K`^g(nsT|l>^JJH=x6MpC+vFIHs^yR3PEjWknqaUUh)6dW=&{yuz zo9NFl^7ukmWN0ut}A z%)_YJGboQLltmNDp$%oAi2Qd(-c6BjHu7wT{JJ5pe#mDmYc?x|wH)W`Ev%iaeXPT* zV%8a4ldD*FSWT>FtTxscmLgjNJ-~=<${vJ1Aix>Q3FA9Ywx6UI%x0&smrMG~@vQZ%wb#C%-~GFP_qx`)Z!%4vW=6B7acOptS-6Ap+5p}WL)${z2@ID_E1;Fo zDrskE4YVfOb=oaj53P?j1fDZWBhVG;Bs!U{Pd9_CgG;xgJJa3iKJ*RrP~iS8px)By z*^q;j&@1U@=neEH`gQs(dJnyiJ_OoflulqMGDr+EL!V*BKp0Gh4a0%q3M}Qv2xdew z5*W#hRLDp28Dd5`qZ)KZBclaawwuul*}+T5Q!q?frYcj5Nnsi@5hjyq!*pP}GQF67 z%wS-(1kfX?%uHrJQ_L)9Rx|6Ejm#Ei2eX^m%Nzi|e#^wLWLc^#Ef$4k%tFA!ZCDO0 zSKvH9Rxm3PSTC8C%F1Nrv&5`&RyC`R)yQfA9_(iILfm@EddtGFW!b9WhZKlk2xuG| zwgcOh?Zx(E2eTvD3G8Ih#hL7Uh-T&NYIYsFk=?@XV0W{7*#qNR2g8vCrK826fRaTx zOpXo5f#b^Y;`o6#M{*KChoTB6pCbl+Q_ZP^OreF-!RhAoat1(~zU5%JvRqZJ*0`=g zKmpot9k{MsFHnNP5HAzB$=p z`C5Dm-g{khPRAsDwy;WiD41Zum(@-1T&kV63MC<1y&fFCj- zNFOj{1}L%y9B~0jc7P>kK$AP*$p;X%0WcK`sEPqxZ2@HM1Z<@Py0QUZ1%R+hK-d|; zSOcJ}32=5Dkai2O)&pqk1H26Z;)VfpqkuXB;7$>cM*{4T0e$*_KQlm}HDHhnD6|6{ zIs+2j0gFDYovdtD31mG@tUf^9FyL;Kg`%y4cMH~{2UepG)?)}(WO#f<6k$zBuqtF& z7kyY6Gguietc@M4j`O$z@!|VH9LVOE@XPsi5DCESk{^i$Bo>fZKw<%j1tb=bSU_R{ zi3KDUkXS%s0f_}97LZs#VgZQ-Bo>fZKw<%j1tb=bSU_R{i3KDU_%~XBNrIE(a2P~L zGL)7x5jlw7C}CxAVj)QrHW6`HEL8!Kla?}7#^Yv5VGw6&c~fa<9wcPoummw5o`%qz zwnYVz!(%aGLNP8GgUp_G9D(Fnqw2vjMJkrYcHNEJ)le*KO>Y+q<&6*_fsP3Ax?(&u zsm9?6o81tlPZxrPE|Ca-{0i_M1Zg79)tRb|%#?97)G&Oi0Eo6AuI%*_@G+CBUN;7O(jd~X+tF8_vfF3P$telbL#x= zf`H)gh|q|j5Gcv*X1>szYJ?b|a}PEcYoX4GSAFkf_PrY`kt_JE`7rgNHoj+cOZ{$FX_vT>v+2g$eIMK%ME3OAiJp7B$w_>nn0j^lM_&WA7xPLk|J<_TaGGk4 z=lj8XjPp*Vz8iDwZ#Vpsv+^|Q7v(){Vtuj=_nP?qmp4o8qIwu(q#e&NJ6>hZ7*&2{ zI~4XZ8h=f3+ihVknbG$6x$%aInh0rcF~O-;DaoTC=?kq(-px1w6ZUJc1d=j_sDL4L zh*<1Lf)s*>KZr8AK?U7F0PIL3I_OxLpg|xJuG~G?1(ZjzIIsP>TS6bJ&2e z(b*J&6+9+rAx(gPF$fAIS#T&RDH$AAV5=bTY4;zkn}Z{kq!y0VhlESVX`a%>u8Ig*yIE59k}ejBGZ! zJ}|S_IMb)kdfyG#;L-5!w{O^sd?gAqTkXC+-@kaRDaW{a)7Y?2u-Bu}PX9atyM5@? zy(qot@B`5;8%un*-YYzumYy{D{ZRay4a(1+_n%mNeU`wb%1$RdXoKy>bBYmN1U=vW zS-q@dBUK-2S^`6@RHFNBXP0~!nOn2z)y<)y(<`Z9qNh0@4>&dq^vr#`l=UP7tU~P?&!R;58!b|7E27%PS3TyB~@`Z11nhQyMw? z#s!;o=UQ>5byCJ)<<*XFuw5!o>pjjFbgr}IW#lZqCACa$=H4RQd$}3JU8^bn=AqJ2 z&HJpM*9V=9yt65(oO-F$iJ*G9in}Oq{_*+yr3ZDJ&8uH@H|MkN|N6u8%dTz()rVX& zcGXtwdMAt@SmmewE?{Iqr@K74p}w>@vze;as@t zf!*YL_YQZtZ?Plil{C#+^6KFCQ;1Oe!hI-Pc{R?NMZ4P2_jE!gt$Jsk-im zE^QfYR!Lp8uiWMJX6+jCyt5*iiWw$ z?Wg%@h;~Igj)Z_)xy3+fGPFh-#Ta;Y9%Z}{MPx)2Y=ssgiIgY-^o0VQy9`dLvn7yhVx{*3*VUo&mHeUEpU#i&Odi_yE{(rJ0d<--nQ-r8cvlBI%mMw%Q2 z%lS`Zk3XP2Np*ejRWC+9_q|)5Zi~_0QT_fvODj z@p-I{%>8^IfeIq#$a8h4k9Tz4h#g~f+9?*fA3OGBBa5aTqKF0+v3xSx)NcI6&m;QD zV>wZ4Er$#;f%?DAGv&Sn>%=+3L#xpFlt99s>JBe+{c84qb zhZxGCTATSl-%PcR7t~wL*%a3LC@lT<^7q{GJVt7q_Iz^rYNPLhs7;Ud9xq5T5%isI zQC40X{cX<`$INYKO{$u6jjKL@4;_+CjP(~fL zfp<NIwg`a?cMDi*8p z4>&IFzRZnssyL*2r|M-2P6nT`tu(+%4%qNNOIosLM^kvcG}x^ zpNxk~jQVXwPl5uu30)7L5UhFaQX4EyN`DhnG@hE>9$%K^UmMnSb9;TOM>n2#Dog#< z%|)5(1aRH+pM6{VQk*xJhZO*Nc&}vxUc$>;y(`TQm<%vrOwM} zt~dX#fyLW#uq;~jBi^`OCq7d*iFV+uGgfaF{bJpL9V^`fZ+FdFsaCYa?7+NTt9}@c zZY-+2ldya1fbI`DHl>L->q;(aRBX(b+T;}Q>}r1KTKl|}Im3a5EzvKI_QAgl~jgjJ&JLj6@O@b;f$W+iSo>7iS zbWXZJyzoBm$MBP_M@R>JUlUSDp8E)o)Ark+-2M98L+^pjw0&FHtCUxtZB{C!X}1=1 zJn~?VX|o)feGksg{XqUEdS%nttANuhtNQf&>w_|b7TMRvog_Y$kIFUG;H^b^p=gEO<4Qtvp zeoYHru2e7_T4(I_&>=VHmVTCJb|m+UyX=9sLpRx`#NH1=jOTWxo>d*k>>KWwW@(mb zgc(Hq62vR}?y+By2~~(&48FPuji2`aYVwQTrtz3|cSoFZ8}4&V5hcVurQb0k1inxE zq^K%W+0YcV{W0+M6#5ZVuTb0kr0in)$w92LZ#%P)xM^p^DN=(F0TO`t6G%#C@7Dje zj9_6w!u}(0@LIW2nZFvmCiORD_!D1p+P$dBU|D{VSGdr7hx>4rK>)|*fISP^9 zhh5vW5&VdPQEK(anaAogP8Kn%+?J>x5t(XBseFTJ7#(6n_hy8KRhedEXOt^%uF#zK l!+aO7&bKbDcU1d3t8bs;)s_DEfFe9@-mqR!Z#Z!?@}FwGg*yNM literal 0 HcmV?d00001