Skip to content

Commit d2085e8

Browse files
committed
Merge remote-tracking branch 'hinyb/feat/interface-terminal' into dev
2 parents ea96e77 + 1753656 commit d2085e8

4 files changed

Lines changed: 368 additions & 0 deletions

File tree

src/main/scala/li/cil/oc/integration/appeng/AEUtil.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ object AEUtil {
122122
AEApi.instance.parts.partInterface != null &&
123123
AEApi.instance.parts.partInterface.sameAsStack(stack)
124124

125+
def isPartInterfaceTerminal(stack: ItemStack): Boolean = stack != null && AEApi.instance != null && {
126+
if (useNewItemDefinitionAPI) isPartInterfaceTerminalNew(stack)
127+
else false
128+
}
129+
130+
private def isPartInterfaceTerminalNew(stack: ItemStack): Boolean =
131+
AEApi.instance.definitions.parts.interfaceTerminal.isSameAs(stack)
132+
125133
def isRobot(stack: ItemStack): Boolean =
126134
api.Items.get(stack) == api.Items.get("robot")
127135

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package li.cil.oc.integration.appeng
2+
3+
import appeng.api.util.DimensionalCoord
4+
import li.cil.oc.api.driver.Converter
5+
6+
import java.util
7+
8+
object ConverterDimensioinalCoord extends Converter {
9+
override def convert(value: AnyRef, output: util.Map[AnyRef, AnyRef]): Unit = value match {
10+
case coord: DimensionalCoord =>
11+
output.put("x", Int.box(coord.x))
12+
output.put("y", Int.box(coord.y))
13+
output.put("z", Int.box(coord.z))
14+
output.put("dimId", Int.box(coord.getDimension))
15+
case _ =>
16+
}
17+
18+
def parse(args: util.Map[_, _], defaultDim: Option[Int] = None): DimensionalCoord = {
19+
def getInt(key: String):Option[Int] = args.get(key) match {
20+
case value: java.lang.Number => Some(value.intValue)
21+
case _ => None
22+
}
23+
new DimensionalCoord(
24+
getInt("x").getOrElse(throw new Exception("Missing x")),
25+
getInt("y").getOrElse(throw new Exception("Missing y")),
26+
getInt("z").getOrElse(throw new Exception("Missing z")),
27+
getInt("dimId").orElse(defaultDim).getOrElse(throw new Exception("Missing dimId")))
28+
}
29+
def parse(args: util.Map[_, _], defaultDim: Int): DimensionalCoord = parse(args, Some(defaultDim))
30+
}
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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+
}

src/main/scala/li/cil/oc/integration/appeng/ModAppEng.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ object ModAppEng extends ModProxy {
2323
Driver.add(DriverExportBus)
2424
Driver.add(DriverImportBus)
2525
Driver.add(DriverPartInterface)
26+
Driver.add(DriverPartInterfaceTerminal)
2627
Driver.add(DriverBlockInterface)
2728
Driver.add(DriverUpgradeAE)
2829

2930
Driver.add(new ConverterCellInventory)
31+
Driver.add(ConverterDimensioinalCoord)
3032
Driver.add(new ConverterPattern)
3133

3234
Driver.add(DriverController.Provider)
3335
Driver.add(DriverExportBus.Provider)
3436
Driver.add(DriverImportBus.Provider)
3537
Driver.add(DriverPartInterface.Provider)
38+
Driver.add(DriverPartInterfaceTerminal.Provider)
3639
Driver.add(DriverBlockInterface.Provider)
3740
Driver.add(DriverUpgradeAE.Provider)
3841

0 commit comments

Comments
 (0)