1+ package li .cil .oc .integration .appeng
2+
3+ import appeng .api .parts .IPartHost
4+ import appeng .api .util .{DimensionalCoord , IInterfaceViewable }
5+ import appeng .core .features .registries .InterfaceTerminalRegistry
6+ import appeng .parts .AEBasePart
7+ import appeng .parts .p2p .PartP2PTunnel
8+ import appeng .parts .reporting .PartInterfaceTerminal
9+ import li .cil .oc .api .driver
10+ import li .cil .oc .api .driver .{EnvironmentProvider , NamedBlock }
11+ import li .cil .oc .api .machine .{Arguments , Callback , Context }
12+ import li .cil .oc .api .prefab .AbstractValue
13+ import li .cil .oc .integration .ManagedTileEntityEnvironment
14+ import li .cil .oc .util .ResultWrapper .result
15+ import net .minecraft .inventory .IInventory
16+ import net .minecraft .item .ItemStack
17+ import net .minecraft .nbt .{NBTTagCompound , NBTTagList }
18+ import net .minecraft .world .World
19+ import net .minecraftforge .common .util .Constants .NBT
20+ import net .minecraftforge .common .util .ForgeDirection
21+
22+ import java .util
23+ import scala .collection .JavaConversions .asScalaSet
24+ import scala .collection .JavaConverters ._
25+ import scala .collection .mutable
26+
27+ object DriverPartInterfaceTerminal extends driver.SidedBlock {
28+ override def worksWith (world : World , x : Int , y : Int , z : Int , side : ForgeDirection ): Boolean =
29+ world.getTileEntity(x, y, z) match {
30+ case container : IPartHost => ForgeDirection .VALID_DIRECTIONS .map(container.getPart).exists(_.isInstanceOf [PartInterfaceTerminal ])
31+ case _ => false
32+ }
33+
34+ override def createEnvironment (world : World , x : Int , y : Int , z : Int , side : ForgeDirection ) = {
35+ val host = world.getTileEntity(x, y, z).asInstanceOf [IPartHost ]
36+ val side = ForgeDirection .VALID_DIRECTIONS .find { dir => host.getPart(dir).isInstanceOf [PartInterfaceTerminal ] }.get
37+ new Environment (host, host.getPart(side).asInstanceOf [PartInterfaceTerminal ])
38+ }
39+
40+ final class Environment (val host : IPartHost , val part : PartInterfaceTerminal ) extends ManagedTileEntityEnvironment [IPartHost ](host, " me_interface_terminal" ) with NamedBlock with PartEnvironmentBase {
41+ override def preferredName = " me_interface_terminal"
42+
43+ override def priority = 0
44+
45+ @ Callback (doc = " function():table -- Returns a list of all interface" )
46+ def getInterfaces (context : Context , args : Arguments ): Array [AnyRef ] = {
47+ result(new InterfaceViewableArrayValue (allMachines))
48+ }
49+
50+ @ Callback (doc = " function(filter:string):table -- Returns a list of interfaces with the specified display name." )
51+ def getInterfacesByName (context : Context , args : Arguments ): Array [AnyRef ] = {
52+ val name = args.checkString(0 )
53+ result(new InterfaceViewableArrayValue (allMachines.filter(_.getName == name)))
54+ }
55+
56+ @ Callback (doc = " function(location:table{x:number, y:number: z:number[, dimId:number]}[, side:number|string]):table -- Returns a list of interfaces at the specified location. 'side' can be a Forge direction number or name." )
57+ def getInterfacesByLocation (context : Context , args : Arguments ): Array [AnyRef ] = {
58+ val location = ConverterDimensioinalCoord .parse(args.checkTable(0 ), host.getLocation.getDimension)
59+ var filtered = allMachines.filter(_.getLocation == location)
60+ if (args.count() >= 2 ) {
61+ val side = if (args.isInteger(1 ))
62+ ForgeDirection .getOrientation(args.checkInteger(1 ))
63+ else
64+ ForgeDirection .valueOf(args.checkString(1 ).toUpperCase)
65+ filtered = filtered.filter {
66+ case part : AEBasePart => part.getSide == side
67+ case _ => ForgeDirection .UNKNOWN == side
68+ }
69+ }
70+ result(new InterfaceViewableArrayValue (filtered))
71+ }
72+
73+ @ Callback (doc = " function(source:table{location:table, slot:number}, target:table{location:table[, slot:number]}):boolean, number|string -- Sends a pattern from source to target. Returns transfer result and target slot or error message." )
74+ def send (context : Context , args : Arguments ): Array [AnyRef ] = {
75+ val sourceData = TransferData .create(args.checkTable(0 ))
76+ if (sourceData.slot == - 1 ) return result(false , " Source slot cannot be empty" )
77+ val targetData = TransferData .create(args.checkTable(1 ))
78+ sendInternal(Array (TransferTask (sourceData, targetData))).head
79+ }
80+
81+ @ Callback (doc = " function(tasks:table{{source:table, target:table}, ...}):table -- Executes multiple transfers in one batch. Returns an array of results, each containing [success:boolean, slotOrError:any]." )
82+ def sendBatch (context : Context , args : Arguments ): Array [AnyRef ] = {
83+ val tasks = mutable.ArrayBuffer [TransferTask ]()
84+ args.checkTable(0 ).asScala.foreach {
85+ case (_, task : util.Map [_, _]) =>
86+ val source = task.get(" source" ) match {
87+ case data : util.Map [_, _] => TransferData .create(data)
88+ case _ => throw new Exception (" Missing source" )
89+ }
90+ val target = task.get(" target" ) match {
91+ case data : util.Map [_, _] => TransferData .create(data)
92+ case _ => throw new Exception (" Missing target" )
93+ }
94+ tasks += TransferTask (source, target)
95+ case _ =>
96+ }
97+ result(sendInternal(tasks))
98+ }
99+
100+ private case class TransferData (location : DimensionalCoord , side : ForgeDirection )(val slot : Int )
101+
102+ private object TransferData {
103+ def create (args : java.util.Map [_, _]): TransferData = {
104+ val location : DimensionalCoord = args.get(" location" ) match {
105+ case location : util.Map [_, _] => ConverterDimensioinalCoord .parse(location, host.getLocation.getDimension)
106+ case _ => throw new Exception (" location is missing" )
107+ }
108+ val side : ForgeDirection = args.get(" side" ) match {
109+ case value : java.lang.Number => ForgeDirection .getOrientation(value.intValue)
110+ case str : String => ForgeDirection .valueOf(str.toUpperCase)
111+ case _ => ForgeDirection .UNKNOWN
112+ }
113+ val slot : Integer = args.get(" slot" ) match {
114+ case value : java.lang.Number => value.intValue
115+ case _ => - 1
116+ }
117+ TransferData (location, side)(slot)
118+ }
119+ }
120+
121+ private case class TransferTask (source : TransferData , target : TransferData )
122+
123+ private def findMachines (targets : Array [TransferData ]) = {
124+ val result = mutable.Map [TransferData , IInterfaceViewable ]()
125+ val it = allMachines.iterator
126+
127+ while (it.hasNext && result.size < targets.length) {
128+ val machine = it.next()
129+ val location = machine.getLocation
130+ val side = machine match {
131+ case part : AEBasePart => part.getSide
132+ case _ => ForgeDirection .UNKNOWN
133+ }
134+ targets.find(t => t.location == location && t.side == side).foreach { matchedData =>
135+ result(matchedData) = machine
136+ }
137+ }
138+ result
139+ }
140+
141+ private def transferPattern (sourceInv : IInventory , sourceSlot : Int , targetInv : IInventory , targetSlot : Int ): Either [String , Int ] = {
142+ if (sourceSlot == - 1 ) return Left (" Source slot cannot be empty" )
143+ if (sourceSlot < 0 || sourceSlot >= sourceInv.getSizeInventory) return Left (" Source slot out of bounds" )
144+ val sourceStack = sourceInv.getStackInSlot(sourceSlot)
145+ if (sourceStack == null ) return Left (" Can't find source pattern" )
146+ val targetSlotActual : Int = if (targetSlot == - 1 ) {
147+ (0 until targetInv.getSizeInventory).find(i => targetInv.getStackInSlot(i) == null ) match {
148+ case Some (emptyIndex) => emptyIndex
149+ case None => return Left (" No empty slot in target machine" )
150+ }
151+ } else {
152+ if (targetSlot < 0 || targetSlot >= targetInv.getSizeInventory) return Left (" Target slot out of bounds" )
153+ val targetStack = targetInv.getStackInSlot(targetSlot)
154+ if (targetStack != null ) return Left (" Target slot is not empty" )
155+ targetSlot
156+ }
157+ targetInv.setInventorySlotContents(targetSlotActual, sourceStack)
158+ sourceInv.setInventorySlotContents(sourceSlot, null )
159+ Right (targetSlotActual)
160+ }
161+
162+ private def sendInternal (tasks : Seq [TransferTask ]) = {
163+ val finds = findMachines(tasks.flatMap(t => Iterator (t.source, t.target)).distinct.toArray)
164+ tasks.view.map { task =>
165+ val sourceData = task.source
166+ val targetData = task.target
167+ (finds.get(sourceData), finds.get(targetData)) match {
168+ case (Some (source), Some (target)) =>
169+ transferPattern(source.getPatterns, sourceData.slot, target.getPatterns, targetData.slot) match {
170+ case Right (slot) => result(true , slot)
171+ case Left (msg) => result(false , msg)
172+ }
173+ case (None , None ) => result(false , " Both machines not found" )
174+ case (None , _) => result(false , " Source machine not found" )
175+ case (_, None ) => result(false , " Target machine not found" )
176+ }
177+ }.toArray
178+ }
179+
180+ private def allMachines : Iterable [IInterfaceViewable ] = {
181+ val grid = getGrid
182+ if (grid == null ) return Array .empty[IInterfaceViewable ]
183+ InterfaceTerminalRegistry .instance.getSupportedClasses.view
184+ .flatMap(c => grid.getMachines(c).asScala)
185+ .map(_.getMachine.asInstanceOf [IInterfaceViewable ])
186+ .filter { m =>
187+ m.shouldDisplay && (m match {
188+ case p : PartP2PTunnel [_] => ! p.isOutput
189+ case _ => true
190+ })
191+ }
192+ }
193+
194+ private def getGrid = part.getActionableNode.getGrid
195+ }
196+
197+ object Provider extends EnvironmentProvider {
198+ override def getEnvironment (stack : ItemStack ): Class [_] =
199+ if (AEUtil .isPartInterfaceTerminal(stack))
200+ classOf [Environment ]
201+ else null
202+ }
203+
204+ class InterfaceViewableArrayValue (it : Iterable [IInterfaceViewable ]) extends AbstractValue {
205+ def this () = this (Iterable .empty)
206+
207+ private case class InterfaceViewableInfo (name : String , location : DimensionalCoord , side : ForgeDirection , patterns : Array [ItemStack ])
208+
209+ private def convert (info : InterfaceViewableInfo , includePatterns : Boolean = true ) = {
210+ if (includePatterns) {
211+ Map (
212+ " name" -> info.name,
213+ " location" -> info.location,
214+ " side" -> info.side,
215+ " patterns" -> info.patterns.zipWithIndex.map(_.swap).toMap
216+ )
217+ } else {
218+ Map (
219+ " name" -> info.name,
220+ " location" -> info.location,
221+ " side" -> info.side
222+ )
223+ }
224+ }
225+
226+ private var array = it.map { machine =>
227+ val name = machine.getName
228+ val location = machine.getLocation
229+ val side = machine match {
230+ case part : AEBasePart => part.getSide
231+ case _ => ForgeDirection .UNKNOWN
232+ }
233+ val patterns = machine.getPatterns
234+ val patternsArray = Array .tabulate(machine.rows() * machine.rowSize()) { index =>
235+ val stack = patterns.getStackInSlot(index)
236+ if (stack == null ) null else stack.copy()
237+ }
238+ InterfaceViewableInfo (name, location, side, patternsArray)
239+ }.toArray
240+ private var index = 0
241+
242+ override def call (context : Context , arguments : Arguments ): Array [AnyRef ] = {
243+ array.lift(index) match {
244+ case Some (info) =>
245+ index += 1
246+ result(convert(info))
247+ case None => result(null )
248+ }
249+ }
250+
251+ override def apply (context : Context , arguments : Arguments ): AnyRef = {
252+ if (arguments.count() == 0 ) return null
253+ if (arguments.isInteger(0 )) {
254+ val luaIndex = arguments.checkInteger(0 )
255+ return array.lift(luaIndex - 1 ) match {
256+ case Some (info) => convert(info)
257+ case None => null
258+ }
259+ }
260+ if (arguments.isString(0 )) {
261+ val arg = arguments.checkString(0 )
262+ if (arg == " n" ) return Int .box(array.length)
263+ }
264+ null
265+ }
266+
267+ override def load (nbt : NBTTagCompound ): Unit = {
268+ index = nbt.getInteger(" index" )
269+
270+ val tagList = nbt.getTagList(" array" , NBT .TAG_LIST )
271+ array = Array .tabulate(tagList.tagCount) { i =>
272+ val el = tagList.getCompoundTagAt(i)
273+ if (el.hasNoTags) {
274+ null
275+ }
276+ else {
277+ val name = el.getString(" name" )
278+ val location = DimensionalCoord .readFromNBT(el)
279+ val side = ForgeDirection .getOrientation(nbt.getInteger(" side" ))
280+ val patternList = nbt.getTagList(" patterns" , NBT .TAG_COMPOUND )
281+ val patterns = Array .tabulate(patternList.tagCount) { j =>
282+ val itemTag = patternList.getCompoundTagAt(j)
283+ if (itemTag.hasNoTags) null else ItemStack .loadItemStackFromNBT(itemTag)
284+ }
285+ InterfaceViewableInfo (name, location, side, patterns)
286+ }
287+ }
288+ }
289+
290+ override def save (nbt : NBTTagCompound ): Unit = {
291+ nbt.setInteger(" index" , index)
292+
293+ val machinesList = new NBTTagList
294+ for (info <- this .array if info != null ) {
295+ val machineTag = new NBTTagCompound
296+ machineTag.setString(" name" , info.name)
297+ info.location.writeToNBT(machineTag)
298+ nbt.setInteger(" side" , info.side.ordinal())
299+ val patternsList = new NBTTagList
300+ info.patterns.foreach { pattern =>
301+ val stackTag = new NBTTagCompound
302+ if (pattern != null ) pattern.writeToNBT(stackTag)
303+ patternsList.appendTag(stackTag)
304+ }
305+ machineTag.setTag(" patterns" , patternsList)
306+ machinesList.appendTag(machineTag)
307+ }
308+ nbt.setTag(" array" , machinesList)
309+ }
310+
311+ @ Callback (doc = " function():nil -- Reset the iterator index so that the next call will return the first element." )
312+ def reset (context : Context , arguments : Arguments ): Array [AnyRef ] = {
313+ index = 0
314+ null
315+ }
316+
317+ @ Callback (doc = " function():number -- Returns the number of elements in the this.array." )
318+ def count (context : Context , arguments : Arguments ): Array [AnyRef ] = result(array.length)
319+
320+ @ Callback (doc = " function([includePatterns:boolean]):table -- Returns ALL the interface info in the this.array. Memory intensive." )
321+ def getAll (context : Context , arguments : Arguments ): Array [AnyRef ] = {
322+ result(array.map(convert(_, arguments.optBoolean(0 , false )).toMap))
323+ }
324+
325+ override def toString = " {InterfaceViewable Array}"
326+ }
327+ }
0 commit comments