diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 380416eb6c5..f526d0a79cc 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -714,4 +714,14 @@
else ..()
/obj/item/proc/pwr_drain()
- return 0 // Process Kill
\ No newline at end of file
+ return 0 // Process Kill
+
+/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/weapon/storage
+ if(!newLoc)
+ return 0
+ if(istype(loc,/obj/item/weapon/storage))
+ var/obj/item/weapon/storage/S = loc
+ S.remove_from_storage(src,newLoc)
+ return 1
+ return 0
+
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 0b461af0861..4e1a5843f60 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -183,6 +183,13 @@ obj/item/device/flashlight/lamp/bananalamp
update_brightness(U)
else
update_brightness(null)
+
+/obj/item/device/flashlight/flare/update_brightness(var/mob/user = null)
+ ..()
+ if(on)
+ item_state = "[initial(item_state)]-on"
+ else
+ item_state = "[initial(item_state)]"
/obj/item/device/flashlight/flare/attack_self(mob/user)
@@ -200,6 +207,15 @@ obj/item/device/flashlight/lamp/bananalamp
src.force = on_damage
src.damtype = "fire"
processing_objects += src
+
+/obj/item/device/flashlight/flare/torch
+ name = "torch"
+ desc = "A torch fashioned from some leaves and a log."
+ w_class = 4
+ brightness_on = 7
+ icon_state = "torch"
+ item_state = "torch"
+ on_damage = 10
/obj/item/device/flashlight/slime
gender = PLURAL
diff --git a/code/game/objects/items/stacks/tiles/light.dm b/code/game/objects/items/stacks/tiles/light.dm
index 943e593183e..3cf7bd35c68 100644
--- a/code/game/objects/items/stacks/tiles/light.dm
+++ b/code/game/objects/items/stacks/tiles/light.dm
@@ -12,13 +12,8 @@
singular_name = "light floor tile"
desc = "A floor tile, made out off glass. Use a multitool on it to change its color."
icon_state = "tile_light blue"
- w_class = 3.0
- force = 3.0
- throwforce = 5.0
- throw_speed = 5
- throw_range = 20
- flags = CONDUCT
- max_amount = 60
+ force = 3
+ throwforce = 5
attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed")
var/on = 1
var/state = LIGHTFLOOR_ON
@@ -49,4 +44,5 @@
icon_state="tile_"+color_desc()
user << "[src] is now "+color_desc()
- return ..()
\ No newline at end of file
+ return ..()
+
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/tiles/plasteel.dm b/code/game/objects/items/stacks/tiles/plasteel.dm
index d40434a1d6e..b864b8f0897 100644
--- a/code/game/objects/items/stacks/tiles/plasteel.dm
+++ b/code/game/objects/items/stacks/tiles/plasteel.dm
@@ -4,14 +4,11 @@
singular_name = "floor tile"
desc = "Those could work as a pretty decent throwing weapon"
icon_state = "tile"
- w_class = 3.0
- force = 6.0
+ force = 6
materials = list(MAT_METAL=500)
- throwforce = 10.0
+ throwforce = 10
throw_speed = 3
throw_range = 7
- flags = CONDUCT
- max_amount = 60
/obj/item/stack/tile/plasteel/New(var/loc, var/amount=null)
..()
@@ -19,28 +16,10 @@
src.pixel_y = rand(1, 14)
return
-/*
-/obj/item/stack/tile/plasteel/attack_self(mob/user as mob)
- if (usr.stat)
- return
- var/T = user.loc
- if (!( istype(T, /turf) ))
- user << "\red You must be on the ground!"
- return
- if (!( istype(T, /turf/space) ))
- user << "\red You cannot build on or repair this turf!"
- return
- src.build(T)
- src.add_fingerprint(user)
- use(1)
- return
-*/
-
/obj/item/stack/tile/plasteel/proc/build(turf/S as turf)
if (istype(S,/turf/space))
S.ChangeTurf(/turf/simulated/floor/plating/airless)
else
S.ChangeTurf(/turf/simulated/floor/plating)
-// var/turf/simulated/floor/W = S.ReplaceWithFloor()
-// W.make_plating()
- return
\ No newline at end of file
+ return
+
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index f54f2481bb6..aa077a6be77 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -1,9 +1,23 @@
-/* Diffrent misc types of tiles
+/* Different misc types of tiles
* Contains:
* Grass
* Wood
* Carpet
*/
+ /obj/item/stack/tile
+ name = "broken tile"
+ singular_name = "broken tile"
+ desc = "A broken tile. This should not exist."
+ icon = 'icons/obj/items.dmi'
+ icon_state = "tile"
+ item_state = "tile"
+ w_class = 3
+ force = 1
+ throwforce = 1
+ throw_speed = 5
+ throw_range = 20
+ max_amount = 60
+ flags = CONDUCT
/*
* Grass
@@ -14,13 +28,6 @@
singular_name = "grass floor tile"
desc = "A patch of grass like they often use on golf courses"
icon_state = "tile_grass"
- w_class = 3.0
- force = 1.0
- throwforce = 1.0
- throw_speed = 5
- throw_range = 20
- flags = CONDUCT
- max_amount = 60
origin_tech = "biotech=1"
/*
@@ -32,13 +39,6 @@
singular_name = "wood floor tile"
desc = "an easy to fit wood floor tile"
icon_state = "tile-wood"
- w_class = 3.0
- force = 1.0
- throwforce = 1.0
- throw_speed = 5
- throw_range = 20
- flags = CONDUCT
- max_amount = 60
/*
* Carpets
@@ -48,10 +48,3 @@
singular_name = "carpet"
desc = "A piece of carpet. It is the same size as a floor tile"
icon_state = "tile-carpet"
- w_class = 3.0
- force = 1.0
- throwforce = 1.0
- throw_speed = 5
- throw_range = 20
- flags = CONDUCT
- max_amount = 60
\ No newline at end of file
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 8e85adf1852..1a0f5ed4023 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -732,8 +732,12 @@ obj/item/toy/cards/deck/MouseDrop(atom/over_object)
else if(istype(over_object, /obj/screen))
switch(over_object.name)
if("l_hand")
+ if(!remove_item_from_storage(M))
+ M.unEquip(src)
M.put_in_l_hand(src)
else if("r_hand")
+ if(!remove_item_from_storage(M))
+ M.unEquip(src)
M.put_in_r_hand(src)
usr << "You pick up the deck."
else
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index c61edc88790..6c057f3cda8 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -96,7 +96,8 @@
var/obj/item/stack/rods/R = I
if (R.use(1))
var/obj/item/weapon/wirerod/W = new /obj/item/weapon/wirerod
- user.unEquip(src)
+ if(!remove_item_from_storage(user))
+ user.unEquip(src)
user.put_in_hands(W)
user << "You wrap the cable restraint around the top of the rod."
qdel(src)
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index a19c240849e..dde656589d9 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -8,6 +8,8 @@
desc = "You wear this on your back and put items into it."
icon_state = "backpack"
item_state = "backpack"
+ lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
w_class = 4.0
slot_flags = SLOT_BACK //ERROOOOO
max_w_class = 3
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index d360ecf9bb7..d22ca34cffa 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -30,7 +30,6 @@
name = "large box"
desc = "You could build a fort with this."
icon_state = "largebox"
- item_state = "largebox"
w_class = 42 // Big, bulky.
foldable = /obj/item/stack/sheet/cardboard //BubbleWrap
storage_slots = 21
diff --git a/code/game/objects/items/weapons/storage/toolbox.dm b/code/game/objects/items/weapons/storage/toolbox.dm
index 51adfcd5a4c..b1efb1168ff 100644
--- a/code/game/objects/items/weapons/storage/toolbox.dm
+++ b/code/game/objects/items/weapons/storage/toolbox.dm
@@ -29,6 +29,7 @@
New()
..()
new /obj/item/weapon/crowbar/red(src)
+ new /obj/item/weapon/weldingtool/mini(src)
new /obj/item/weapon/extinguisher/mini(src)
if(prob(50))
new /obj/item/device/flashlight(src)
@@ -83,10 +84,10 @@
New()
..()
- new /obj/item/weapon/screwdriver(src)
+ new /obj/item/weapon/screwdriver(src, "red")
new /obj/item/weapon/wrench(src)
- new /obj/item/weapon/weldingtool(src)
- new /obj/item/weapon/crowbar(src)
- new /obj/item/weapon/wirecutters(src)
+ new /obj/item/weapon/weldingtool/largetank(src)
+ new /obj/item/weapon/crowbar/red(src)
+ new /obj/item/weapon/wirecutters(src, "red")
new /obj/item/device/multitool(src)
new /obj/item/clothing/gloves/combat(src)
\ No newline at end of file
diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm
index 8de3b2b0483..9549727af4d 100644
--- a/code/game/objects/items/weapons/tools.dm
+++ b/code/game/objects/items/weapons/tools.dm
@@ -112,8 +112,9 @@
sharp = 1
edge = 1
-/obj/item/weapon/wirecutters/New()
- if(prob(50))
+/obj/item/weapon/wirecutters/New(loc, var/param_color = null)
+ ..()
+ if((!param_color && prob(50)) || param_color == "yellow")
icon_state = "cutters-y"
item_state = "cutters_yellow"
@@ -136,15 +137,16 @@
name = "welding tool"
icon = 'icons/obj/items.dmi'
icon_state = "welder"
+ item_state = "welder"
flags = CONDUCT
slot_flags = SLOT_BELT
//Amount of OUCH when it's thrown
- force = 3.0
- throwforce = 5.0
+ force = 3
+ throwforce = 5
throw_speed = 3
throw_range = 5
- w_class = 2.0
+ w_class = 2
//Cost to make in the autolathe
materials = list(MAT_METAL=70, MAT_GLASS=30)
@@ -156,132 +158,137 @@
var/welding = 0 //Whether or not the welding tool is off(0), on(1) or currently welding(2)
var/status = 1 //Whether the welder is secured or unsecured (able to attach rods to it to make a flamethrower)
var/max_fuel = 20 //The max amount of fuel the welder can hold
+ var/change_icons = 1
+ var/can_off_process = 0
+ var/light_intensity = 2 //how powerful the emitted light is when used.
/obj/item/weapon/weldingtool/New()
-// var/random_fuel = min(rand(10,20),max_fuel)
- var/datum/reagents/R = new/datum/reagents(max_fuel)
- reagents = R
- R.my_atom = src
- R.add_reagent("fuel", max_fuel)
+ ..()
+ create_reagents(max_fuel)
+ reagents.add_reagent("fuel", max_fuel)
+ update_icon()
return
-
-
+
/obj/item/weapon/weldingtool/examine(mob/user)
if(!..(user, 0))
- user << text("\icon[] [] contains []/[] units of fuel!", src, src.name, get_fuel(),src.max_fuel )
+ user << "It contains [get_fuel()] unit\s of fuel out of [max_fuel]."
+
+/obj/item/weapon/weldingtool/suicide_act(mob/user)
+ user.visible_message("[user] welds \his every orifice closed! It looks like \he's trying to commit suicide..")
+ return (FIRELOSS)
+
+/obj/item/weapon/weldingtool/proc/update_torch()
+ overlays.Cut()
+ if(welding)
+ overlays += "[initial(icon_state)]-on"
+ item_state = "[initial(item_state)]1"
+ else
+ item_state = "[initial(item_state)]"
-
-
-/obj/item/weapon/weldingtool/attackby(obj/item/W as obj, mob/user as mob, params)
- if(istype(W,/obj/item/weapon/screwdriver))
- if(welding)
- user << "\red Stop welding first!"
- return
- status = !status
- if(status)
- user << "\blue You resecure the welder."
+/obj/item/weapon/weldingtool/update_icon()
+ if(change_icons)
+ var/ratio = get_fuel() / max_fuel
+ ratio = Ceiling(ratio*4) * 25
+ if(ratio == 100)
+ icon_state = initial(icon_state)
else
- user << "\blue The welder can now be attached and modified."
- src.add_fingerprint(user)
- return
-
- if((!status) && (istype(W,/obj/item/stack/rods)))
- var/obj/item/stack/rods/R = W
- R.use(1)
- var/obj/item/weapon/flamethrower/F = new/obj/item/weapon/flamethrower(user.loc)
- src.loc = F
- F.weldtool = src
- if (user.client)
- user.client.screen -= src
- if (user.r_hand == src)
- user.unEquip(src)
- else
- user.unEquip(src)
- src.master = F
- src.layer = initial(src.layer)
- user.unEquip(src)
- if (user.client)
- user.client.screen -= src
- src.loc = F
- src.add_fingerprint(user)
- return
-
- ..()
+ icon_state = "[initial(icon_state)][ratio]"
+ update_torch()
return
+/obj/item/weapon/weldingtool/attackby(obj/item/I as obj, mob/user as mob, params)
+ if(istype(I, /obj/item/weapon/screwdriver))
+ flamethrower_screwdriver(I, user)
+ if(istype(I, /obj/item/stack/rods))
+ flamethrower_rods(I, user)
+ ..()
/obj/item/weapon/weldingtool/process()
switch(welding)
- //If off
if(0)
- if(src.icon_state != "welder") //Check that the sprite is correct, if it isnt, it means toggle() was not called
- src.force = 3
- src.damtype = "brute"
- src.icon_state = "welder"
- src.welding = 0
- processing_objects.Remove(src)
+ force = 3
+ damtype = "brute"
+ update_icon()
+ if(!can_off_process)
+ processing_objects.Remove(src)
return
- //Welders left on now use up fuel, but lets not have them run out quite that fast
+ //Welders left on now use up fuel, but lets not have them run out quite that fast
if(1)
- if(src.icon_state != "welder1") //Check that the sprite is correct, if it isnt, it means toggle() was not called
- src.force = 15
- src.damtype = "fire"
- src.icon_state = "welder1"
+ force = 15
+ damtype = "fire"
if(prob(5))
remove_fuel(1)
+ update_icon()
- //If you're actually actively welding, use fuel faster.
- //Is this actually used or set anywhere? - Nodrak
- if(2)
- if(prob(75))
- remove_fuel(1)
-
-
- //I'm not sure what this does. I assume it has to do with starting fires...
- //...but it doesnt check to see if the welder is on or not.
- var/turf/location = src.loc
- if(istype(location, /mob/))
+ //This is to start fires. process() is only called if the welder is on.
+ var/turf/location = loc
+ if(ismob(location))
var/mob/M = location
if(M.l_hand == src || M.r_hand == src)
location = get_turf(M)
- if (istype(location, /turf))
+ if(isturf(location))
location.hotspot_expose(700, 5)
+/obj/item/weapon/weldingtool/attack(mob/M as mob, mob/user as mob)
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/external/S = H.organs_by_name[user.zone_sel.selecting]
+ if (!S)
+ return
+
+ if(!(S.status & ORGAN_ROBOT) || user.a_intent != I_HELP || S.open == 2)
+ return ..()
+
+ if(S.brute_dam)
+ if(S.brute_dam < ROBOLIMB_SELF_REPAIR_CAP)
+ if (remove_fuel(0,null))
+ playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
+ S.heal_damage(15,0,0,1)
+ user.visible_message("\The [user] patches some dents on \the [M]'s [S.name] with \the [src].")
+ else if(S.open != 2)
+ user << "Need more welding fuel!"
+ return 1
+ else
+ user << "The damage is far too severe to patch over externally."
+ return 1
+ else if(S.open != 2)
+ user << "Nothing to fix!"
+ else
+ return ..()
/obj/item/weapon/weldingtool/afterattack(obj/O as obj, mob/user as mob, proximity)
if(!proximity) return
- if (istype(O, /obj/structure/reagent_dispensers/fueltank) && get_dist(src,O) <= 1 && !src.welding)
- O.reagents.trans_to(src, max_fuel)
- user << "\blue Welder refueled"
- playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -6)
- return
- else if (istype(O, /obj/structure/reagent_dispensers/fueltank) && get_dist(src,O) <= 1 && src.welding)
- msg_admin_attack("[key_name_admin(user)] triggered a fueltank explosion.")
- log_game("[key_name(user)] triggered a fueltank explosion.")
- user << "\red That was stupid of you."
- var/obj/structure/reagent_dispensers/fueltank/tank = O
- tank.explode()
- return
- if (src.welding)
+ if(istype(O, /obj/structure/reagent_dispensers/fueltank) && in_range(src, O))
+ if(!welding)
+ O.reagents.trans_to(src, max_fuel)
+ user << "[src] refueled."
+ playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -6)
+ update_icon()
+ return
+ else
+ message_admins("[key_name_admin(user)] triggered a fueltank explosion.")
+ log_game("[key_name(user)] triggered a fueltank explosion.")
+ user << "That was stupid of you."
+ O.ex_act()
+ return
+
+ if(welding)
remove_fuel(1)
var/turf/location = get_turf(user)
- if (istype(location, /turf))
- location.hotspot_expose(700, 50, 1)
+ location.hotspot_expose(700, 50, 1)
+
if(isliving(O))
var/mob/living/L = O
L.IgniteMob()
- return
-
-/obj/item/weapon/weldingtool/attack_self(mob/user as mob)
- toggle()
- return
+/obj/item/weapon/weldingtool/attack_self(mob/user)
+ toggle(user)
+ update_icon()
//Returns the amount of fuel in the welder
/obj/item/weapon/weldingtool/proc/get_fuel()
return reagents.get_reagent_amount("fuel")
-
//Removes fuel from the welding tool. If a mob is passed, it will perform an eyecheck on the mob. This should probably be renamed to use()
/obj/item/weapon/weldingtool/proc/remove_fuel(var/amount = 1, var/mob/M = null)
if(!welding || !check_fuel())
@@ -299,67 +306,48 @@
//Returns whether or not the welding tool is currently on.
/obj/item/weapon/weldingtool/proc/isOn()
- return src.welding
-
-//Sets the welding state of the welding tool. If you see W.welding = 1 anywhere, please change it to W.setWelding(1)
-//so that the welding tool updates accordingly
-/obj/item/weapon/weldingtool/proc/setWelding(var/temp_welding)
- //If we're turning it on
- if(temp_welding > 0)
- if (get_fuel() > 0)
- usr << "\blue The [src] switches on."
- src.force = 15
- src.damtype = "fire"
- src.icon_state = "welder1"
- processing_objects.Add(src)
- else
- usr << "\blue Need more fuel!"
- src.welding = 0
- return
- //Otherwise
- else
- usr << "\blue The [src] switches off."
- src.force = 3
- src.damtype = "brute"
- src.icon_state = "welder"
- src.welding = 0
+ return welding
//Turns off the welder if there is no more fuel (does this really need to be its own proc?)
-/obj/item/weapon/weldingtool/proc/check_fuel()
- if((get_fuel() <= 0) && welding)
- toggle(1)
+/obj/item/weapon/weldingtool/proc/check_fuel(mob/user)
+ if(get_fuel() <= 0 && welding)
+ toggle(user, 1)
+ update_icon()
+ //mob icon update
+ if(ismob(loc))
+ var/mob/M = loc
+ M.update_inv_r_hand(0)
+ M.update_inv_l_hand(0)
+
return 0
return 1
-
//Toggles the welder off and on
-/obj/item/weapon/weldingtool/proc/toggle(var/message = 0)
- if(!status) return
- src.welding = !( src.welding )
- if (src.welding)
- if (remove_fuel(1))
- usr << "\blue You switch the [src] on."
- src.w_class = 4
- src.force = 15
- src.damtype = "fire"
- hitsound = "sound/items/welder.ogg"
- src.icon_state = "welder1"
- processing_objects.Add(src)
+/obj/item/weapon/weldingtool/proc/toggle(mob/user, message = 0)
+ if(!status)
+ user << "[src] can't be turned on while unsecured!"
+ return
+ welding = !welding
+ if(welding)
+ if(get_fuel() >= 1)
+ user << "You switch [src] on."
+ force = 15
+ damtype = "fire"
+ hitsound = 'sound/items/welder.ogg'
+ update_icon()
+ processing_objects |= src
else
- usr << "\blue Need more fuel!"
- src.welding = 0
- return
+ user << "You need more fuel!"
+ welding = 0
else
if(!message)
- usr << "\blue You switch the [src] off."
+ user << "You switch [src] off."
else
- usr << "\blue The [src] shuts off!"
- src.w_class = 2
- src.force = 3
- src.damtype = "brute"
+ user << "[src] shuts off!"
+ force = 3
+ damtype = "brute"
hitsound = "swing_hit"
- src.icon_state = "welder"
- src.welding = 0
+ update_icon()
//Decides whether or not to damage a player's eyes based on what they're wearing as protection
//Note: This should probably be moved to mob
@@ -387,10 +375,8 @@
user.eye_blurry += rand(12,20)
E.damage += rand(12, 16)
if(safety<2)
-
if(E.damage > 10)
user << "Your eyes are really starting to hurt. This can't be good for you!"
-
if (E.damage >= E.min_broken_damage)
user << "You go blind!"
user.sdisabilities |= BLIND
@@ -402,16 +388,64 @@
spawn(100)
user.disabilities &= ~NEARSIGHTED
return
+
+/obj/item/weapon/weldingtool/proc/flamethrower_screwdriver(obj/item/I, mob/user)
+ if(welding)
+ user << "Turn it off first!"
+ return
+ status = !status
+ if(status)
+ user << "You resecure [src]."
+ else
+ user << "[src] can now be attached and modified."
+ add_fingerprint(user)
+/obj/item/weapon/weldingtool/proc/flamethrower_rods(obj/item/I, mob/user)
+ if(!status)
+ var/obj/item/stack/rods/R = I
+ if (R.use(1))
+ var/obj/item/weapon/flamethrower/F = new /obj/item/weapon/flamethrower(user.loc)
+ if(!remove_item_from_storage(F))
+ user.unEquip(src)
+ loc = F
+ F.weldtool = src
+ add_fingerprint(user)
+ user << "You add a rod to a welder, starting to build a flamethrower."
+ user.put_in_hands(F)
+ else
+ user << "You need one rod to start building a flamethrower!"
+ return
/obj/item/weapon/weldingtool/largetank
name = "Industrial Welding Tool"
+ desc = "A slightly larger welder with a larger tank."
+ icon_state = "indwelder"
max_fuel = 40
materials = list(MAT_METAL=70, MAT_GLASS=60)
origin_tech = "engineering=2"
+/obj/item/weapon/weldingtool/largetank/cyborg
+
+/obj/item/weapon/weldingtool/largetank/flamethrower_screwdriver()
+ return
+
+/obj/item/weapon/weldingtool/mini
+ name = "emergency welding tool"
+ desc = "A miniature welder used during emergencies."
+ icon_state = "miniwelder"
+ max_fuel = 10
+ w_class = 1
+ materials = list(MAT_METAL=30, MAT_GLASS=10)
+ change_icons = 0
+
+/obj/item/weapon/weldingtool/mini/flamethrower_screwdriver()
+ return
+
/obj/item/weapon/weldingtool/hugetank
name = "Upgraded Welding Tool"
+ desc = "An upgraded welder based of the industrial welder."
+ icon_state = "upindwelder"
+ item_state = "upindwelder"
max_fuel = 80
w_class = 3.0
materials = list(MAT_METAL=70, MAT_GLASS=120)
@@ -419,20 +453,29 @@
/obj/item/weapon/weldingtool/experimental
name = "Experimental Welding Tool"
+ desc = "An experimental welder capable of self-fuel generation and less harmful to the eyes."
+ icon_state = "exwelder"
+ item_state = "exwelder"
max_fuel = 40
w_class = 3.0
materials = list(MAT_METAL=70, MAT_GLASS=120)
origin_tech = "engineering=4;plasma=3"
- icon_state = "ewelder"
var/last_gen = 0
+ change_icons = 0
+ can_off_process = 1
+ light_intensity = 1
+/obj/item/weapon/weldingtool/experimental/proc/fuel_gen()
+ if(!welding && !last_gen)
+ last_gen = 1
+ reagents.add_reagent("fuel",1)
+ spawn(10)
+ last_gen = 0
-
-/obj/item/weapon/weldingtool/experimental/proc/fuel_gen()//Proc to make the experimental welder generate fuel, optimized as fuck -Sieve
- var/gen_amount = ((world.time-last_gen)/25)
- reagents += (gen_amount)
- if(reagents > max_fuel)
- reagents = max_fuel
+/obj/item/weapon/weldingtool/experimental/process()
+ ..()
+ if(reagents.total_volume < max_fuel)
+ fuel_gen()
/*
* Crowbar
@@ -468,35 +511,6 @@
materials = list(MAT_METAL=70)
icon_state = "crowbar_large"
-/obj/item/weapon/weldingtool/attack(mob/M as mob, mob/user as mob)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/S = H.organs_by_name[user.zone_sel.selecting]
-
- if (!S)
- return
- if(!(S.status & ORGAN_ROBOT) || user.a_intent != I_HELP || S.open == 2)
- return ..()
-
- if(S.brute_dam)
- if(S.brute_dam < ROBOLIMB_SELF_REPAIR_CAP)
- if (remove_fuel(0,null))
- playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
- S.heal_damage(15,0,0,1)
- user.visible_message("\The [user] patches some dents on \the [M]'s [S.name] with \the [src].")
- else if(S.open != 2)
- user << "Need more welding fuel!"
- return 1
- else
- user << "The damage is far too severe to patch over externally."
- return 1
- else if(S.open != 2)
- user << "Nothing to fix!"
-
- else
- return ..()
-
-
/obj/item/weapon/conversion_kit
name = "\improper Revolver Conversion Kit"
desc = "A professional conversion kit used to convert any knock off revolver into the real deal capable of shooting lethal .357 rounds without the possibility of catastrophic failure"
diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm
index e80dd1d04a1..520de21301c 100644
--- a/code/game/objects/items/weapons/weaponry.dm
+++ b/code/game/objects/items/weapons/weaponry.dm
@@ -159,9 +159,10 @@ obj/item/weapon/wirerod/attackby(var/obj/item/I, mob/user as mob, params)
..()
if(istype(I, /obj/item/weapon/shard))
var/obj/item/weapon/twohanded/spear/S = new /obj/item/weapon/twohanded/spear
-
+
+ if(!remove_item_from_storage(user))
+ user.unEquip(src)
user.unEquip(I)
- user.unEquip(src)
user.put_in_hands(S)
user << "You fasten the glass shard to the top of the rod with the cable."
@@ -171,8 +172,9 @@ obj/item/weapon/wirerod/attackby(var/obj/item/I, mob/user as mob, params)
else if(istype(I, /obj/item/weapon/wirecutters))
var/obj/item/weapon/melee/baton/cattleprod/P = new /obj/item/weapon/melee/baton/cattleprod
+ if(!remove_item_from_storage(user))
+ user.unEquip(src)
user.unEquip(I)
- user.unEquip(src)
user.put_in_hands(P)
user << "You fasten the wirecutters to the top of the rod with the cable, prongs outward."
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index d562098cc21..a37eeabcd9b 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -35,13 +35,16 @@
if((!D)||(!D2)) return 0
if((!isassembly(D))||(!isassembly(D2))) return 0
if((D:secured)||(D2:secured)) return 0
- if(user)
- user.remove_from_mob(D)
- user.remove_from_mob(D2)
+ if(!D.remove_item_from_storage(src))
+ if(user)
+ user.remove_from_mob(D)
+ D.loc = src
+ if(!D2.remove_item_from_storage(src))
+ if(user)
+ user.remove_from_mob(D2)
+ D2.loc = src
D:holder = src
D2:holder = src
- D.loc = src
- D2.loc = src
a_left = D
a_right = D2
name = "[D.name]-[D2.name] assembly"
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index e55ee2d1149..b32dcd649b6 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -868,7 +868,7 @@ var/global/list/damage_icon_parts = list()
var/t_state = r_hand.item_state
if(!t_state) t_state = r_hand.icon_state
- var/image/I = image("icon" = r_hand.righthand_file, "icon_state"="[t_state]", "layer"=-R_HAND_LAYER)
+ var/image/I = image("icon" = r_hand.righthand_file, "icon_state"="[t_state]")
I = center_image(I, r_hand.inhand_x_dimension, r_hand.inhand_y_dimension)
overlays_standing[R_HAND_LAYER] = I
@@ -884,7 +884,7 @@ var/global/list/damage_icon_parts = list()
var/t_state = l_hand.item_state
if(!t_state) t_state = l_hand.icon_state
- var/image/I = image("icon" = l_hand.lefthand_file, "icon_state"="[t_state]", "layer"=-L_HAND_LAYER)
+ var/image/I = image("icon" = l_hand.lefthand_file, "icon_state"="[t_state]")
I = center_image(I, l_hand.inhand_x_dimension, l_hand.inhand_y_dimension)
overlays_standing[L_HAND_LAYER] = I
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 035c199a521..9ba128a8b23 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -171,7 +171,7 @@
src.modules += new /obj/item/borg/sight/meson(src)
src.modules += new /obj/item/weapon/rcd/borg(src)
src.modules += new /obj/item/weapon/extinguisher(src)
- src.modules += new /obj/item/weapon/weldingtool/largetank(src)
+ src.modules += new /obj/item/weapon/weldingtool/largetank/cyborg(src)
src.modules += new /obj/item/weapon/screwdriver(src)
src.modules += new /obj/item/weapon/wrench(src)
src.modules += new /obj/item/weapon/crowbar(src)
@@ -412,7 +412,7 @@
)
/obj/item/weapon/robot_module/drone/New()
- src.modules += new /obj/item/weapon/weldingtool(src)
+ src.modules += new /obj/item/weapon/weldingtool/largetank/cyborg(src)
src.modules += new /obj/item/weapon/screwdriver(src)
src.modules += new /obj/item/weapon/wrench(src)
src.modules += new /obj/item/weapon/crowbar(src)
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index 71e64b61823..552f55051d1 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -36,12 +36,12 @@
else if(istype(over_object, /obj/screen))
switch(over_object.name)
if("r_hand")
- if(!M.unEquip(src))
- return
+ if(!remove_item_from_storage(M))
+ M.unEquip(src)
M.put_in_r_hand(src)
if("l_hand")
- if(!M.unEquip(src))
- return
+ if(!remove_item_from_storage(M))
+ M.unEquip(src)
M.put_in_l_hand(src)
add_fingerprint(M)
diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm
index ffa0b3de900..03340af09b7 100644
--- a/code/modules/projectiles/guns/projectile.dm
+++ b/code/modules/projectiles/guns/projectile.dm
@@ -143,6 +143,7 @@
desc = "A universal syndicate small-arms suppressor for maximum espionage."
icon = 'icons/obj/gun.dmi'
icon_state = "suppressor"
+ item_state = "suppressor"
w_class = 2
var/oldsound = null
var/initial_w_class = null
\ No newline at end of file
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index 8860c560021..54704eceb5b 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -236,7 +236,8 @@
name = "\improper Thompson SMG"
desc = "A genuine 'Chicago Typewriter'."
icon_state = "tommygun"
- item_state = "tommygun"
+ item_state = "shotgun"
+ w_class = 5
slot_flags = 0
origin_tech = "combat=5;materials=1;syndicate=2"
mag_type = "/obj/item/ammo_box/magazine/tommygunm45"
diff --git a/code/modules/reagents/reagent_containers/food/drinks.dm b/code/modules/reagents/reagent_containers/food/drinks.dm
index a25f3f2657e..8180bb2561f 100644
--- a/code/modules/reagents/reagent_containers/food/drinks.dm
+++ b/code/modules/reagents/reagent_containers/food/drinks.dm
@@ -169,7 +169,7 @@
desc = "A golden cup"
name = "golden cup"
icon_state = "golden_cup"
- item_state = "" //nope :(
+ item_state = "golden_cup" //yup :)
w_class = 4
force = 14
throwforce = 10
diff --git a/code/modules/research/designs/equipment_designs.dm b/code/modules/research/designs/equipment_designs.dm
index 9cdefac2cb6..330098a2383 100644
--- a/code/modules/research/designs/equipment_designs.dm
+++ b/code/modules/research/designs/equipment_designs.dm
@@ -1,6 +1,16 @@
/////////////////////////////////////////
/////////////////Equipment///////////////
/////////////////////////////////////////
+/datum/design/exwelder
+ name = "Experimental Welding Tool"
+ desc = "An experimental welder capable of self-fuel generation."
+ id = "exwelder"
+ req_tech = list("materials" = 4, "engineering" = 4, "bluespace" = 3, "plasmatech" = 3)
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_PLASMA = 1500, MAT_URANIUM = 200)
+ build_path = /obj/item/weapon/weldingtool/experimental
+ category = list("Equipment")
+
/datum/design/health_hud
name = "Health Scanner HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their health status."
diff --git a/icons/mob/inhands/guns_lefthand.dmi b/icons/mob/inhands/guns_lefthand.dmi
index f3209d8c047..78f6918e029 100644
Binary files a/icons/mob/inhands/guns_lefthand.dmi and b/icons/mob/inhands/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/guns_righthand.dmi b/icons/mob/inhands/guns_righthand.dmi
index 25ac8d3c072..cce966fddc6 100644
Binary files a/icons/mob/inhands/guns_righthand.dmi and b/icons/mob/inhands/guns_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 2e0181bab59..ea5dd0ef128 100644
Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index aa1bb8522af..a280235db96 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi
index 09ae246998e..36b08f19a09 100644
Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ
diff --git a/icons/obj/lighting.dmi b/icons/obj/lighting.dmi
index f417a982a64..d4c4b244809 100644
Binary files a/icons/obj/lighting.dmi and b/icons/obj/lighting.dmi differ
diff --git a/tools/dmitool/README.txt b/tools/dmitool/README.txt
new file mode 100644
index 00000000000..409ab873c3d
--- /dev/null
+++ b/tools/dmitool/README.txt
@@ -0,0 +1,5 @@
+Uses PNGJ: https://code.google.com/p/pngj/.
+
+For help, use "java -jar dmitool.jar help".
+
+Requires Java 7.
\ No newline at end of file
diff --git a/tools/dmitool/build.gradle b/tools/dmitool/build.gradle
new file mode 100644
index 00000000000..2e5fbc5b08e
--- /dev/null
+++ b/tools/dmitool/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile group: 'ar.com.hjg', name: 'pngj', version: '2.1.0'
+}
+
+jar {
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ manifest {
+ attributes 'Main-Class': 'dmitool.Main'
+ }
+}
\ No newline at end of file
diff --git a/tools/dmitool/dmimerge.sh b/tools/dmitool/dmimerge.sh
new file mode 100644
index 00000000000..dbe846fc6a9
--- /dev/null
+++ b/tools/dmitool/dmimerge.sh
@@ -0,0 +1,8 @@
+java -jar tools/dmitool/dmitool.jar merge $1 $2 $3 $2
+if [ "$?" -gt 0 ]
+then
+ echo "Unable to automatically resolve all icon_state conflicts, please merge manually."
+ exit 1
+fi
+
+exit 0
diff --git a/tools/dmitool/dmitool.jar b/tools/dmitool/dmitool.jar
new file mode 100644
index 00000000000..4cfa49f36de
Binary files /dev/null and b/tools/dmitool/dmitool.jar differ
diff --git a/tools/dmitool/dmitool.py b/tools/dmitool/dmitool.py
new file mode 100644
index 00000000000..390f0d745ff
--- /dev/null
+++ b/tools/dmitool/dmitool.py
@@ -0,0 +1,94 @@
+""" Python 2.7 wrapper for dmitool.
+"""
+
+import os
+from subprocess import Popen, PIPE
+
+_JAVA_PATH = ["java"]
+_DMITOOL_CMD = ["-jar", "dmitool.jar"]
+
+def _dmitool_call(*dmitool_args, **popen_args):
+ return Popen(_JAVA_PATH + _DMITOOL_CMD + [str(arg) for arg in dmitool_args], **popen_args)
+
+def _safe_parse(dict, key, deferred_value):
+ try:
+ dict[key] = deferred_value()
+ except Exception as e:
+ print "Could not parse property '%s': %s"%(key, e)
+ return e
+ return False
+
+def version():
+ """ Returns the version as a string. """
+ stdout, stderr = _dmitool_call("version", stdout=PIPE).communicate()
+ return str(stdout).strip()
+
+def help():
+ """ Returns the help text as a string. """
+ stdout, stderr = _dmitool_call("help", stdout=PIPE).communicate()
+ return str(stdout).strip()
+
+def info(filepath):
+ """ Totally not a hack that parses the output from dmitool into a dictionary.
+ May break at any moment.
+ """
+ subproc = _dmitool_call("info", filepath, stdout=PIPE)
+ stdout, stderr = subproc.communicate()
+
+ result = {}
+ data = stdout.split(os.linesep)[1:]
+ #for s in data: print s
+
+ #parse header line
+ if len(data) > 0:
+ header = data.pop(0).split(",")
+ #don't need to parse states, it's redundant
+ _safe_parse(result, "images", lambda: int(header[0].split()[0].strip()))
+ _safe_parse(result, "size", lambda: header[2].split()[1].strip())
+
+ #parse state information
+ states = []
+ for item in data:
+ if not len(item): continue
+
+ stateinfo = {}
+ item = item.split(",", 3)
+ _safe_parse(stateinfo, "name", lambda: item[0].split()[1].strip(" \""))
+ _safe_parse(stateinfo, "dirs", lambda: int(item[1].split()[0].strip()))
+ _safe_parse(stateinfo, "frames", lambda: int(item[2].split()[0].strip()))
+ if len(item) > 3:
+ stateinfo["misc"] = item[3]
+
+ states.append(stateinfo)
+
+ result["states"] = states
+ return result
+
+def extract_state(input_path, output_path, icon_state, direction=None, frame=None):
+ """ Extracts an icon state as a png to a given path.
+ If provided direction should be a string, one of S, N, E, W, SE, SW, NE, NW.
+ If provided frame should be a frame number or a string of two frame number separated by a dash.
+ """
+ args = ["extract", input_path, icon_state, output_path]
+ if direction is not None: args.extend(("direction" , str(direction)))
+ if frame is not None: args.extend(("frame" , str(frame)))
+ return _dmitool_call(*args)
+
+def import_state(target_path, input_path, icon_state, replace=False, delays=None, rewind=False, loop=None, ismovement=False, direction=None, frame=None):
+ """ Inserts an input png given by the input_path into the target_path.
+ """
+ args = ["import", target_path, icon_state, input_path]
+
+ if replace: args.append("nodup")
+ if rewind: args.append("rewind")
+ if ismovement: args.append("movement")
+ if delays: args.extend(("delays", ",".join(delays)))
+ if direction is not None: args.extend(("direction", direction))
+ if frame is not None: args.extend(("frame", frame))
+
+ if loop in ("inf", "infinity"):
+ args.append("loop")
+ elif loop:
+ args.extend(("loopn", loop))
+
+ return _dmitool_call(*args)
diff --git a/tools/dmitool/git_merge_installer.bat b/tools/dmitool/git_merge_installer.bat
new file mode 100644
index 00000000000..c7ba2fa23a1
--- /dev/null
+++ b/tools/dmitool/git_merge_installer.bat
@@ -0,0 +1,6 @@
+@echo off
+set tab=
+echo. >> ../../.git/config
+echo [merge "merge-dmi"] >> ../../.git/config
+echo %tab%name = iconfile merge driver >> ../../.git/config
+echo %tab%driver = ./tools/dmitool/dmimerge.sh %%O %%A %%B >> ../../.git/config
diff --git a/tools/dmitool/git_merge_installer.sh b/tools/dmitool/git_merge_installer.sh
new file mode 100644
index 00000000000..e0c48823b11
--- /dev/null
+++ b/tools/dmitool/git_merge_installer.sh
@@ -0,0 +1,6 @@
+F="../../.git/config"
+
+echo '' >> $F
+echo '[merge "merge-dmi"]' >> $F
+echo ' name = iconfile merge driver' >> $F
+echo ' driver = ./tools/dmitool/dmimerge.sh %O %A %B' >> $F
diff --git a/tools/dmitool/merging.txt b/tools/dmitool/merging.txt
new file mode 100644
index 00000000000..639a634bfb7
--- /dev/null
+++ b/tools/dmitool/merging.txt
@@ -0,0 +1,14 @@
+1. Install java(http://www.java.com/en/download/index.jsp)
+2. Make sure java is in your PATH. To test this, open git bash, and type "java". If it says unknown command, you need to add JAVA/bin to your PATH variable (A guide for this can be found at https://www.java.com/en/download/help/path.xml ).
+
+Merging
+The easiest way to do merging is to install the merge driver. For this, open `Baystation12/.git/config` in a text editor, and paste the following lines to the end of it:
+
+[merge "merge-dmi"]
+ name = iconfile merge driver
+ driver = ./tools/dmitool/dmimerge.sh %O %A %B
+
+You may optionally instead run git_merge_installer.bat or git_merge_installer.sh which should automatically insert these lines for you at the appropriate location.
+
+After this, merging DMI files should happen automagically unless there are conflicts (an icon_state that both you and someone else changed).
+If there are conflicts, you will unfortunately still be stuck with opening both versions in the editor, and manually resolving the issues with those states.
diff --git a/tools/dmitool/src/main/java/dmitool/DMI.java b/tools/dmitool/src/main/java/dmitool/DMI.java
new file mode 100644
index 00000000000..bb560107ae9
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/DMI.java
@@ -0,0 +1,455 @@
+package dmitool;
+
+import ar.com.hjg.pngj.ImageInfo;
+import ar.com.hjg.pngj.ImageLineInt;
+import ar.com.hjg.pngj.PngReader;
+import ar.com.hjg.pngj.PngWriter;
+import ar.com.hjg.pngj.PngjInputException;
+import ar.com.hjg.pngj.chunks.PngChunkPLTE;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+
+public class DMI implements Comparator {
+ int w, h;
+ List images;
+ int totalImages = 0;
+ RGBA[] palette;
+ boolean isPaletted;
+
+ public DMI(int w, int h) {
+ this.w = w;
+ this.h = h;
+ images = new ArrayList<>();
+ isPaletted = false;
+ palette = null;
+ }
+
+ public DMI(String f) throws DMIException, FileNotFoundException {
+ this(new File(f));
+ }
+
+ public DMI(File f) throws DMIException, FileNotFoundException {
+ if(f.length() == 0) { // Empty .dmi is empty file
+ w = 32;
+ h = 32;
+ images = new ArrayList<>();
+ isPaletted = false;
+ palette = null;
+ return;
+ }
+ InputStream in = new FileInputStream(f);
+ PngReader pngr;
+ try {
+ pngr = new PngReader(in);
+ } catch(PngjInputException pie) {
+ throw new DMIException("Bad file format!", pie);
+ }
+ String descriptor = pngr.getMetadata().getTxtForKey("Description");
+ String[] lines = descriptor.split("\n");
+
+ if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + lines.length + " lines.");
+ if(Main.VERBOSITY > 3) {
+ System.out.println("Descriptor:");
+ System.out.println(descriptor);
+ }
+
+ /* length 6 is:
+ # BEGIN DMI
+ version = 4.0
+ state = "state"
+ dirs = 1
+ frames = 1
+ # END DMI
+ */
+ if(lines.length < 6) throw new DMIException(null, 0, "Descriptor too short!");
+
+ if(!"# BEGIN DMI".equals(lines[0])) throw new DMIException(lines, 0, "Expected '# BEGIN DMI'");
+ if(!"# END DMI".equals(lines[lines.length-1])) throw new DMIException(lines, lines.length-1, "Expected '# END DMI'");
+ if(!"version = 4.0".equals(lines[1])) throw new DMIException(lines, 1, "Unknown version, expected 'version = 4.0'");
+
+ this.w = 32;
+ this.h = 32;
+
+ int i = 2;
+
+ if(lines[i].startsWith("\twidth = ")) {
+ this.w = Integer.parseInt(lines[2].substring("\twidth = ".length()));
+ i++;
+ }
+ if(lines[i].startsWith("\theight = ")) {
+ this.h = Integer.parseInt(lines[3].substring("\theight = ".length()));
+ i++;
+ }
+
+ List states = new ArrayList<>();
+
+ while(i < lines.length - 1) {
+ long imagesInState = 1;
+ if(!lines[i].startsWith("state = \"") || !lines[i].endsWith("\"")) throw new DMIException(lines, i, "Error reading state string");
+ String stateName = lines[i].substring("state = \"".length(), lines[i].length()-1);
+ i++;
+ int dirs = 1;
+ int frames = 1;
+ float[] delays = null;
+ boolean rewind = false;
+ int loop = -1;
+ String hotspot = null;
+ boolean movement = false;
+ while(lines[i].startsWith("\t")) {
+ if(lines[i].startsWith("\tdirs = ")) {
+ dirs = Integer.parseInt(lines[i].substring("\tdirs = ".length()));
+ imagesInState *= dirs;
+ i++;
+ } else if(lines[i].startsWith("\tframes = ")) {
+ frames = Integer.parseInt(lines[i].substring("\tframes = ".length()));
+ imagesInState *= frames;
+ i++;
+ } else if(lines[i].startsWith("\tdelay = ")) {
+ String delayString = lines[i].substring("\tdelay = ".length());
+ String[] delayVals = delayString.split(",");
+ delays = new float[delayVals.length];
+ for(int d=0; d 0) System.out.println(pal.getNentries() + " palette entries");
+
+ palette = new RGBA[pal.getNentries()];
+ int[] rgb = new int[3];
+ for(int q=0; q 0) System.out.println("Non-paletted image");
+ }
+
+ int iw = pngr.imgInfo.cols;
+ int ih = pngr.imgInfo.rows;
+
+ if(totalImages > iw * ih)
+ throw new DMIException(null, 0, "Impossible number of images!");
+
+ if(Main.VERBOSITY > 0) System.out.println("Image size " + iw+"x"+ih);
+ int[][] px = new int[ih][];
+
+ for(int y=0; y statesY)
+ // this should NEVER happen, we pre-check it
+ throw new DMIException(null, 0, "CRITICAL: End of image reached with states to go!");
+ }
+ }
+ if(is.delays != null) {
+ if((Main.STRICT && is.delays.length*is.dirs != img.length) || is.delays.length*is.dirs < img.length)
+ throw new DMIException(null, 0, "Delay array size mismatch: " + is.delays.length*is.dirs + " vs " + img.length + "!");
+ }
+ is.images = img;
+ }
+ }
+
+ public IconState getIconState(String name) {
+ for(IconState is: images) {
+ if(is.name.equals(name)) {
+ return is;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Makes a copy, unless name is null.
+ */
+ public void addIconState(String name, IconState is) {
+ if(name == null) {
+ images.add(is);
+ totalImages += is.dirs * is.frames;
+ } else {
+ IconState newState = (IconState)is.clone();
+ newState.name = name;
+ images.add(newState);
+ totalImages += is.dirs * is.frames;
+ }
+ }
+
+ public boolean removeIconState(String name) {
+ for(IconState is: images) {
+ if(is.name.equals(name)) {
+ images.remove(is);
+ totalImages -= is.dirs * is.frames;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean setIconState(IconState is) {
+ for(int i=0; i 0) System.out.println("Fixing PNG chunks...");
+ out.writeInt(in.readInt());
+ out.writeInt(in.readInt());
+
+ Deque notZTXT = new ArrayDeque<>();
+
+ PNGChunk c = null;
+
+ while(c == null || c.type != IEND) {
+ c = new PNGChunk(in);
+ if(c.type == zTXt && notZTXT != null) {
+ PNGChunk cc = null;
+ while(cc == null || cc.type != IHDR) {
+ cc = notZTXT.pop();
+ cc.write(out);
+ }
+ c.write(out);
+ while(notZTXT.size() != 0) {
+ PNGChunk pc = notZTXT.pop();
+ pc.write(out);
+ }
+ notZTXT = null;
+ } else if(notZTXT != null) {
+ notZTXT.add(c);
+ } else {
+ c.write(out);
+ }
+ }
+ if(Main.VERBOSITY > 0) System.out.println("Chunks fixed.");
+ }
+
+ @Override public int compare(IconState arg0, IconState arg1) {
+ return arg0.name.compareTo(arg1.name);
+ }
+
+ public void writeDMI(OutputStream os) throws IOException {
+ writeDMI(os, false);
+ }
+ public void writeDMI(OutputStream os, boolean sortStates) throws IOException {
+ if(totalImages == 0) { // Empty .dmis are empty files
+ os.close();
+ return;
+ }
+
+ // Setup chunk-fix buffer
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ if(sortStates) {
+ Collections.sort(images, this);
+ }
+
+ // Write the dmi into the buffer
+ int sx = (int)Math.ceil(Math.sqrt(totalImages));
+ int sy = totalImages / sx;
+ if(sx*sy < totalImages) {
+ sy++;
+ }
+ if(Main.VERBOSITY > 0) System.out.println("Image size: " + w + "x" + h + "; number of images " + sx + "x" + sy + " (" + totalImages + ")");
+ int ix = sx * w;
+ int iy = sy * h;
+ ImageInfo ii = new ImageInfo(ix, iy, 8, true);
+ PngWriter out = new PngWriter(baos, ii);
+ out.setCompLevel(9); // Maximum compression
+ String description = getDescriptor();
+ if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + (description.split("\n").length) + " lines.");
+ out.getMetadata().setText("Description", description, true, true);
+
+ Image[][] img = new Image[sx][sy];
+ {
+ int k = 0;
+ int r = 0;
+ for(IconState is: images) {
+ for(Image i: is.images) {
+ img[k++][r] = i;
+
+ if(k == sx) {
+ k = 0;
+ r++;
+ }
+ }
+ }
+ }
+
+ for(int irow=0; irow myIS = new HashMap<>();
+ HashMap dmiIS = new HashMap<>();
+
+ for(IconState is: images) {
+ myIS.put(is.name, is);
+ }
+ for(IconState is: dmi.images) {
+ dmiIS.put(is.name, is);
+ }
+ if(!myIS.keySet().equals(dmiIS.keySet())) return false;
+ for(String s: myIS.keySet()) {
+ if(!myIS.get(s).equals(dmiIS.get(s))) return false;
+ }
+
+ return true;
+ }
+}
diff --git a/tools/dmitool/src/main/java/dmitool/DMIDiff.java b/tools/dmitool/src/main/java/dmitool/DMIDiff.java
new file mode 100644
index 00000000000..431d9c1d15a
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/DMIDiff.java
@@ -0,0 +1,194 @@
+package dmitool;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DMIDiff {
+ Map newIconStates;
+ Map modifiedIconStates = new HashMap<>();
+ Set removedIconStates;
+
+ DMIDiff() {
+ newIconStates = new HashMap<>();
+ removedIconStates = new HashSet<>();
+ }
+
+ public DMIDiff(DMI base, DMI mod) {
+ if(base.h != mod.h || base.w != mod.w) throw new IllegalArgumentException("Cannot compare non-identically-sized DMIs!");
+
+ HashMap baseIS = new HashMap<>();
+ for(IconState is: base.images) {
+ baseIS.put(is.name, is);
+ }
+
+ HashMap modIS = new HashMap<>();
+ for(IconState is: mod.images) {
+ modIS.put(is.name, is);
+ }
+
+ newIconStates = ((HashMap)modIS.clone());
+ for(String s: baseIS.keySet()) {
+ newIconStates.remove(s);
+ }
+
+ removedIconStates = new HashSet<>();
+ removedIconStates.addAll(baseIS.keySet());
+ removedIconStates.removeAll(modIS.keySet());
+
+ Set retainedStates = new HashSet<>();
+ retainedStates.addAll(baseIS.keySet());
+ retainedStates.retainAll(modIS.keySet());
+
+ for(String s: retainedStates) {
+ if(!baseIS.get(s).equals(modIS.get(s))) {
+ modifiedIconStates.put(s, new IconStateDiff(baseIS.get(s), modIS.get(s)));
+ }
+ }
+ }
+ /**
+ * ASSUMES NO MERGE CONFLICTS - MERGE DIFFS FIRST.
+ */
+ public void applyToDMI(DMI dmi) {
+ for(String s: removedIconStates) {
+ dmi.removeIconState(s);
+ }
+ for(String s: modifiedIconStates.keySet()) {
+ dmi.setIconState(modifiedIconStates.get(s).newState);
+ }
+ for(String s: newIconStates.keySet()) {
+ dmi.addIconState(null, newIconStates.get(s));
+ }
+ }
+
+ /**
+ * @param other The diff to merge with
+ * @param conflictDMI A DMI to add conflicted icon_states to
+ * @param merged An empty DMIDiff to merge into
+ * @param aName The log name for this diff
+ * @param bName The log name for {@code other}
+ * @return A Set containing all icon_states which conflicted, along with what was done in each diff, in the format "icon_state: here|there"; here and there are one of "added", "modified", and "removed"
+ */
+ public Set mergeDiff(DMIDiff other, DMI conflictDMI, DMIDiff merged, String aName, String bName) {
+ HashSet myTouched = new HashSet<>();
+ myTouched.addAll(removedIconStates);
+ myTouched.addAll(newIconStates.keySet());
+ myTouched.addAll(modifiedIconStates.keySet());
+
+ HashSet otherTouched = new HashSet<>();
+ otherTouched.addAll(other.removedIconStates);
+ otherTouched.addAll(other.newIconStates.keySet());
+ otherTouched.addAll(other.modifiedIconStates.keySet());
+
+ HashSet bothTouched = (HashSet)myTouched.clone();
+ bothTouched.retainAll(otherTouched); // this set now contains the list of icon_states that *both* diffs modified, which we'll put in conflictDMI for manual merge (unless they were deletions
+
+ if(Main.VERBOSITY > 0) {
+ System.out.println("a: " + Arrays.toString(myTouched.toArray()));
+ System.out.println("b: " + Arrays.toString(otherTouched.toArray()));
+ System.out.println("both: " + Arrays.toString(bothTouched.toArray()));
+ }
+
+ HashSet whatHappened = new HashSet<>();
+
+ for(String s: bothTouched) {
+ String here, there;
+ if(removedIconStates.contains(s)) {
+ here = "removed";
+ } else if(newIconStates.containsKey(s)) {
+ here = "added";
+ } else if(modifiedIconStates.containsKey(s)) {
+ here = "modified";
+ } else {
+ System.out.println("Unknown error; state="+s);
+ here = "???";
+ }
+
+ if(other.removedIconStates.contains(s)) {
+ there = "removed";
+ } else if(other.newIconStates.containsKey(s)) {
+ there = "added";
+ } else if(other.modifiedIconStates.containsKey(s)) {
+ there = "modified";
+ } else {
+ System.out.println("Unknown error; state="+s);
+ there = "???";
+ }
+
+ whatHappened.add(s + ": " + here + "|" + there);
+ }
+
+ // Removals
+ for(String s: removedIconStates) {
+ if(!bothTouched.contains(s)) {
+ merged.removedIconStates.add(s);
+ }
+ }
+ for(String s: other.removedIconStates) {
+ if(!bothTouched.contains(s)) {
+ merged.removedIconStates.add(s);
+ }
+ }
+
+ // Modifications
+ for(String s: modifiedIconStates.keySet()) {
+ if(!bothTouched.contains(s)) {
+ merged.modifiedIconStates.put(s, modifiedIconStates.get(s));
+ } else {
+ conflictDMI.addIconState(aName + "|" + s, modifiedIconStates.get(s).newState);
+ }
+ }
+ for(String s: other.modifiedIconStates.keySet()) {
+ if(!bothTouched.contains(s)) {
+ merged.modifiedIconStates.put(s, other.modifiedIconStates.get(s));
+ } else {
+ conflictDMI.addIconState(bName + "|" + s, other.modifiedIconStates.get(s).newState);
+ }
+ }
+
+ // Additions
+ for(String s: newIconStates.keySet()) {
+ if(!bothTouched.contains(s)) {
+ merged.newIconStates.put(s, newIconStates.get(s));
+ } else {
+ conflictDMI.addIconState(aName + s, newIconStates.get(s));
+ }
+ }
+ for(String s: other.newIconStates.keySet()) {
+ if(!bothTouched.contains(s)) {
+ merged.newIconStates.put(s, other.newIconStates.get(s));
+ } else {
+ conflictDMI.addIconState(bName + s, other.newIconStates.get(s));
+ }
+ }
+
+ return whatHappened;
+ }
+
+ @Override public String toString() {
+ String s = "";
+ String t = "\t";
+ String q = "\"";
+ String n = "\n";
+ if(!removedIconStates.isEmpty()) {
+ s += "Removed:\n";
+ for(String state: removedIconStates)
+ s += t + q + state + q + n;
+ }
+ if(!modifiedIconStates.isEmpty()) {
+ s += "Modified:\n";
+ for(String state: modifiedIconStates.keySet())
+ s += t + q + state + q + " [" + modifiedIconStates.get(state).toString() + "]\n";
+ }
+ if(!newIconStates.isEmpty()) {
+ s += "Added:\n";
+ for(String state: newIconStates.keySet())
+ s += t + q + state + q + " " + newIconStates.get(state).infoStr() + n;
+ }
+ if("".equals(s))
+ return "No changes";
+ return s;
+ }
+}
\ No newline at end of file
diff --git a/tools/dmitool/src/main/java/dmitool/DMIException.java b/tools/dmitool/src/main/java/dmitool/DMIException.java
new file mode 100644
index 00000000000..e9293162894
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/DMIException.java
@@ -0,0 +1,24 @@
+package dmitool;
+
+public class DMIException extends Exception {
+ String[] desc = null;
+ int line = 0;
+ public DMIException(String[] descriptor, int line, String what) {
+ super(what);
+ desc = descriptor;
+ this.line = line;
+ }
+ public DMIException(String what) {
+ super(what);
+ }
+ public DMIException(String what, Exception cause) {
+ super(what, cause);
+ }
+
+ @Override public String getMessage() {
+ if(desc != null)
+ return "\"" + desc[line] + "\" - " + super.getMessage();
+
+ return super.getMessage();
+ }
+}
\ No newline at end of file
diff --git a/tools/dmitool/src/main/java/dmitool/IconState.java b/tools/dmitool/src/main/java/dmitool/IconState.java
new file mode 100644
index 00000000000..2a2202c71cd
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/IconState.java
@@ -0,0 +1,280 @@
+package dmitool;
+
+import java.util.Arrays;
+import ar.com.hjg.pngj.ImageInfo;
+import ar.com.hjg.pngj.ImageLineInt;
+import ar.com.hjg.pngj.PngWriter;
+import ar.com.hjg.pngj.PngReader;
+import ar.com.hjg.pngj.PngjInputException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class IconState {
+ String name;
+ int dirs;
+ int frames;
+ float[] delays;
+ Image[] images; // dirs come first
+ boolean rewind;
+ int loop;
+ String hotspot;
+ boolean movement;
+
+ public String getInfoLine() {
+ String extraInfo = "";
+ if(rewind) extraInfo += " rewind";
+ if(frames != 1) {
+ extraInfo += " loop(" + (loop==-1 ? "infinite" : loop) + ")";
+ }
+ if(hotspot != null) extraInfo += " hotspot('" + hotspot + "')";
+ if(movement) extraInfo += " movement";
+ if(extraInfo.equals("")) {
+ return String.format("state \"%s\", %d dir(s), %d frame(s)", name, dirs, frames);
+ } else {
+ return String.format("state \"%s\", %d dir(s), %d frame(s),%s", name, dirs, frames, extraInfo);
+ }
+ }
+
+ @Override public IconState clone() {
+ IconState is = new IconState(name, dirs, frames, images.clone(), delays==null ? null : delays.clone(), rewind, loop, hotspot, movement);
+ is.delays = delays != null ? delays.clone() : null;
+ is.rewind = rewind;
+
+ return is;
+ }
+
+ public IconState(String name, int dirs, int frames, Image[] images, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) {
+ if(delays != null) {
+ if(Main.STRICT && delays.length != frames) {
+ throw new IllegalArgumentException("Delays and frames must be the same length!");
+ }
+ }
+ this.name = name;
+ this.dirs = dirs;
+ this.frames = frames;
+ this.images = images;
+ this.rewind = rewind;
+ this.loop = loop;
+ this.hotspot = hotspot;
+ this.delays = delays;
+ this.movement = movement;
+ }
+ void setDelays(float[] delays) {
+ this.delays = delays;
+ }
+ void setRewind(boolean b) {
+ rewind = b;
+ }
+ @Override public boolean equals(Object obj) {
+ if(obj == this) return true;
+ if(!(obj instanceof IconState)) return false;
+
+ IconState is = (IconState)obj;
+
+ if(!is.name.equals(name)) return false;
+ if(is.dirs != dirs) return false;
+ if(is.frames != frames) return false;
+ if(!Arrays.equals(images, is.images)) return false;
+ if(is.rewind != rewind) return false;
+ if(is.loop != loop) return false;
+ if(!Arrays.equals(delays, is.delays)) return false;
+ if(!(is.hotspot == null ? hotspot == null : is.hotspot.equals(hotspot))) return false;
+ if(is.movement != movement) return false;
+
+ return true;
+ }
+ public String infoStr() {
+ return "[" + frames + " frame(s), " + dirs + " dir(s)]";
+ }
+ public String getDescriptorFragment() {
+ String s = "";
+ String q = "\"";
+ String n = "\n";
+ s += "state = " + q + name + q + n;
+ s += "\tdirs = " + dirs + n;
+ s += "\tframes = " + frames + n;
+ if(delays != null) {
+ s += "\tdelay = " + delayArrayToString(delays) + n;
+ }
+ if(rewind) {
+ s += "\trewind = 1\n";
+ }
+ if(loop != -1) {
+ s += "\tloop = " + loop + n;
+ }
+ if(hotspot != null) {
+ s += "\thotspot = " + hotspot + n;
+ }
+ if(movement) {
+ s += "\tmovement = 1\n";
+ }
+ return s;
+ }
+
+ private static String delayArrayToString(float[] d) {
+ String s = "";
+ for(float f: d) {
+ s += ","+f;
+ }
+ return s.substring(1);
+ }
+
+ /**
+ * Dump the state to the given OutputStream in PNG format. Frames will be dumped along the X axis of the image, and directions will be dumped along the Y.
+ */
+ public void dumpToPNG(OutputStream outS, int minDir, int maxDir, int minFrame, int maxFrame) {
+ int totalDirs = maxDir - minDir + 1;
+ int totalFrames = maxFrame - minFrame + 1;
+
+ int w = images[minDir + minFrame * this.dirs].w;
+ int h = images[minDir + minFrame * this.dirs].h;
+
+ if(Main.VERBOSITY > 0) System.out.println("Writing " + totalDirs + " dir(s), " + totalFrames + " frame(s), " + totalDirs*totalFrames + " image(s) total.");
+ ImageInfo ii = new ImageInfo(totalFrames * w, totalDirs * h, 8, true);
+ PngWriter out = new PngWriter(outS, ii);
+ out.setCompLevel(9);
+
+ Image[][] img = new Image[totalFrames][totalDirs];
+ {
+ for(int i=0; i= frames)
+ throw new IllegalArgumentException("Provided frame is out of range: " + frame);
+ if(dir < 0 || dir >= dirs)
+ throw new IllegalArgumentException("Provided dir is out of range: " + dir);
+
+ images[getIndex(dir, frame)] = splice;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/dmitool/src/main/java/dmitool/IconStateDiff.java b/tools/dmitool/src/main/java/dmitool/IconStateDiff.java
new file mode 100644
index 00000000000..a5d30db7fe0
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/IconStateDiff.java
@@ -0,0 +1,126 @@
+package dmitool;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class IconStateDiff {
+ static class ISAddress {
+ int dir;
+ int frame;
+
+ public ISAddress(int dir, int frame) {
+ this.dir = dir;
+ this.frame = frame;
+ }
+
+ public String infoStr(int maxDir, int maxFrame) {
+ if(maxDir == 1 && maxFrame == 1) {
+ return "";
+ } else if(maxDir == 1) {
+ return "{" + frame + "}";
+ } else if(maxFrame == 1) {
+ return "{" + Main.dirs[dir] + "}";
+ } else {
+ return "{" + Main.dirs[dir] + " " + frame + "}";
+ }
+ }
+ }
+ int oldFrameCount = 0;
+ int oldDirectionCount = 0;
+ boolean oldRewind = false;
+ int oldLoop = -1;
+ String oldHotspot = null;
+
+ int newFrameCount = 0;
+ int newDirectionCount = 0;
+ boolean newRewind = false;
+ int newLoop = -1;
+ String newHotspot = null;
+
+ IconState newState;
+ HashMap modifiedFrames = new HashMap<>();
+ HashMap newFrames = new HashMap<>();
+ HashSet removedFrames = new HashSet<>();
+
+ public IconStateDiff(IconState base, IconState mod) {
+ int maxDir = Math.max(base.dirs, mod.dirs);
+ int maxFrame = Math.max(base.frames, mod.frames);
+
+ oldFrameCount = base.frames;
+ oldDirectionCount = base.dirs;
+ oldRewind = base.rewind;
+ oldLoop = base.loop;
+ oldHotspot = base.hotspot;
+
+ newFrameCount = mod.frames;
+ newDirectionCount = mod.dirs;
+ newRewind = mod.rewind;
+ newLoop = mod.loop;
+ newHotspot = mod.hotspot;
+
+ newState = mod;
+
+ Image baseI, modI;
+ for(int d=0; d d && base.frames > f) {
+ baseI = base.images[f * base.dirs + d];
+ } else baseI = null;
+ if(mod.dirs > d && mod.frames > f) {
+ modI = mod.images[f * mod.dirs + d];
+ } else modI = null;
+
+ if(baseI == null && modI == null) continue;
+
+ if(baseI == null) newFrames.put(new ISAddress(d, f), modI);
+ else if(modI == null) removedFrames.add(new ISAddress(d, f));
+ else if(!baseI.equals(modI)) {
+ modifiedFrames.put(new ISAddress(d, f), modI);
+ }
+ }
+ }
+ }
+
+ @Override public String toString() {
+ String s = "";
+ String tmp;
+
+ if(newDirectionCount != oldDirectionCount)
+ s += " | dirs " + oldDirectionCount + "->" + newDirectionCount;
+
+ if(newFrameCount != oldFrameCount)
+ s += " | frames " + oldFrameCount + "->" + newFrameCount;
+
+ if(newRewind != oldRewind) {
+ s += " | rewind " + oldRewind + "->" + newRewind;
+ }
+
+ if(newLoop != oldLoop) {
+ s += " | loop " + oldLoop + "->" + newLoop;
+ }
+
+ if(newHotspot == null ? oldHotspot != null : !newHotspot.equals(oldHotspot)) {
+ s += " | hotspot " + oldHotspot + "->" + newHotspot;
+ }
+
+ if(!modifiedFrames.isEmpty()) {
+ int total_frames = Math.min(oldFrameCount, newFrameCount) * Math.min(oldDirectionCount, newDirectionCount);
+ tmp = "";
+ for(ISAddress isa: modifiedFrames.keySet()) {
+ String str = isa.infoStr(oldDirectionCount, oldFrameCount);
+ if(!"".equals(str)) {
+ tmp += ", " + str;
+ }
+ }
+ if(!"".equals(tmp)) {
+ s += " | modified " + modifiedFrames.size() + " of " + total_frames + ": " + tmp.substring(1);
+ } else {
+ s += " | modified " + modifiedFrames.size() + " of " + total_frames;
+ }
+ }
+
+ if("".equals(s))
+ return "No change";
+ return s.substring(3);
+ }
+}
\ No newline at end of file
diff --git a/tools/dmitool/src/main/java/dmitool/Image.java b/tools/dmitool/src/main/java/dmitool/Image.java
new file mode 100644
index 00000000000..2ea7ff41920
--- /dev/null
+++ b/tools/dmitool/src/main/java/dmitool/Image.java
@@ -0,0 +1,32 @@
+package dmitool;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public abstract class Image {
+ int w, h;
+
+ abstract RGBA getPixel(int x, int y);
+
+ public Image(int w, int h) {
+ this.w = w;
+ this.h = h;
+ }
+
+ @Override public boolean equals(Object obj) {
+ if(obj == this) return true;
+ if(!(obj instanceof Image)) return false;
+
+ Image im = (Image) obj;
+
+ if(w != im.w || h != im.h) return false;
+
+ for(int i=0; i argq = new ArrayDeque<>();
+ for(String s: args) {
+ argq.addLast(s);
+ }
+ if(argq.size() == 0) {
+ System.out.println("No command found; use 'help' for help");
+ return;
+ }
+ String switches = argq.peekFirst();
+ if(switches.startsWith("-")) {
+ for(char c: switches.substring(1).toCharArray()) {
+ switch(c) {
+ case 'v': VERBOSITY++; break;
+ case 'q': VERBOSITY--; break;
+ case 'S': STRICT = true; break;
+ }
+ }
+ argq.pollFirst();
+ }
+ String op = argq.pollFirst();
+
+ switch(op) {
+ case "diff": {
+ if(argq.size() < 2) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String a = argq.pollFirst();
+ String b = argq.pollFirst();
+
+ if(VERBOSITY >= 0) System.out.println("Loading " + a);
+ DMI dmi = doDMILoad(a);
+ if(VERBOSITY >= 0) dmi.printInfo();
+
+ if(VERBOSITY >= 0) System.out.println("Loading " + b);
+ DMI dmi2 = doDMILoad(b);
+ if(VERBOSITY >= 0) dmi2.printInfo();
+
+ DMIDiff dmid = new DMIDiff(dmi, dmi2);
+ System.out.println(dmid);
+ break;
+ }
+ case "sort": {
+ if(argq.size() < 1) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String f = argq.pollFirst();
+
+ if(VERBOSITY >= 0) System.out.println("Loading " + f);
+ DMI dmi = doDMILoad(f);
+ if(VERBOSITY >= 0) dmi.printInfo();
+
+ if(VERBOSITY >= 0) System.out.println("Saving " + f);
+ dmi.writeDMI(new FileOutputStream(f), true);
+ break;
+ }
+ case "merge": {
+ if(argq.size() < 4) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String baseF = argq.pollFirst(),
+ aF = argq.pollFirst(),
+ bF = argq.pollFirst(),
+ mergedF = argq.pollFirst();
+ if(VERBOSITY >= 0) System.out.println("Loading " + baseF);
+ DMI base = doDMILoad(baseF);
+ if(VERBOSITY >= 0) base.printInfo();
+
+ if(VERBOSITY >= 0) System.out.println("Loading " + aF);
+ DMI aDMI = doDMILoad(aF);
+ if(VERBOSITY >= 0) aDMI.printInfo();
+
+ if(VERBOSITY >= 0) System.out.println("Loading " + bF);
+ DMI bDMI = doDMILoad(bF);
+ if(VERBOSITY >= 0) bDMI.printInfo();
+
+ DMIDiff aDiff = new DMIDiff(base, aDMI);
+ DMIDiff bDiff = new DMIDiff(base, bDMI);
+ DMIDiff mergedDiff = new DMIDiff();
+ DMI conflictDMI = new DMI(32, 32);
+
+ Set cf = aDiff.mergeDiff(bDiff, conflictDMI, mergedDiff, aF, bF);
+
+ mergedDiff.applyToDMI(base);
+
+ base.writeDMI(new FileOutputStream(mergedF));
+
+ if(!cf.isEmpty()) {
+ if(VERBOSITY >= 0) for(String s: cf) {
+ System.out.println(s);
+ }
+ conflictDMI.writeDMI(new FileOutputStream(mergedF + ".conflict.dmi"), true);
+ System.out.println("Add/modify conflicts placed in '" + mergedF + ".conflict.dmi'");
+ System.exit(1); // Git expects non-zero on merge conflict
+ } else {
+ System.out.println("No conflicts");
+ System.exit(0);
+ }
+ break;
+ }
+ case "extract": {
+ if(argq.size() < 3) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String file = argq.pollFirst(),
+ state = argq.pollFirst(),
+ outFile = argq.pollFirst();
+
+ DMI dmi = doDMILoad(file);
+ if(VERBOSITY >= 0) dmi.printInfo();
+
+ IconState is = dmi.getIconState(state);
+ if(is == null) {
+ System.out.println("icon_state '"+state+"' does not exist!");
+ return;
+ }
+ // minDir, Maxdir, minFrame, Maxframe
+ int mDir=0, Mdir=is.dirs-1;
+ int mFrame=0, Mframe=is.frames-1;
+
+ while(argq.size() > 1) {
+ String arg = argq.pollFirst();
+
+ switch(arg) {
+ case "d":
+ case "dir":
+ case "dirs":
+ case "direction":
+ case "directions":
+ String dString = argq.pollFirst();
+ if(dString.contains("-")) {
+ String[] splitD = dString.split("-");
+ if(splitD.length == 2) {
+ mDir = parseDir(splitD[0], is);
+ Mdir = parseDir(splitD[1], is);
+ } else {
+ System.out.println("Illegal dir string: '" + dString + "'!");
+ return;
+ }
+ } else {
+ mDir = parseDir(dString, is);
+ Mdir = mDir;
+ }
+ // Invalid value check, warnings are printed in parseDir()
+ if(mDir == -1 || Mdir == -1) return;
+ if(Mdir < mDir) {
+ System.out.println("Maximum dir greater than minimum dir!");
+ System.out.println("Textual direction order is S, N, E, W, SE, SW, NE, NW increasing 0 (S) to 7 (NW)");
+ return;
+ }
+ break;
+ case "f":
+ case "frame":
+ case "frames":
+ String fString = argq.pollFirst();
+ if(fString.contains("-")) {
+ String[] splitF = fString.split("-");
+ if(splitF.length == 2) {
+ mFrame = parseFrame(splitF[0], is);
+ Mframe = parseFrame(splitF[1], is);
+ } else {
+ System.out.println("Illegal frame string: '" + fString + "'!");
+ return;
+ }
+ } else {
+ mFrame = parseFrame(fString, is);
+ Mframe = mFrame;
+ }
+ // Invalid value check, warnings are printed in parseFrame()
+ if(mFrame == -1 || Mframe == -1) return;
+ if(Mframe < mFrame) {
+ System.out.println("Maximum frame greater than minimum frame!");
+ return;
+ }
+ break;
+ default:
+ System.out.println("Unknown argument '" + arg + "' detected, ignoring.");
+ }
+ }
+ if(!argq.isEmpty()) {
+ System.out.println("Extra argument '" + argq.pollFirst() + "' detected, ignoring.");
+ }
+ is.dumpToPNG(new FileOutputStream(outFile), mDir, Mdir, mFrame, Mframe);
+ break;
+ }
+ case "import": {
+ if(argq.size() < 3) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String dmiFile = argq.pollFirst(),
+ stateName = argq.pollFirst(),
+ pngFile = argq.pollFirst();
+
+ boolean noDup = false;
+ boolean rewind = false;
+ int loop = 0;
+ boolean movement = false;
+ String hotspot = null;
+ float[] delays = null;
+ String replaceDir = null;
+ String replaceFrame = null;
+ while(!argq.isEmpty()) {
+ String s = argq.pollFirst();
+ switch(s.toLowerCase()) {
+ case "nodup":
+ case "nd":
+ case "n":
+ noDup = true;
+ break;
+ case "rewind":
+ case "rw":
+ case "r":
+ rewind = true;
+ break;
+ case "loop":
+ case "lp":
+ case "l":
+ loop = -1;
+ break;
+ case "loopn":
+ case "lpn":
+ case "ln":
+ if(!argq.isEmpty()) {
+ String loopTimes = argq.pollFirst();
+ try {
+ loop = Integer.parseInt(loopTimes);
+ } catch(NumberFormatException nfe) {
+ System.out.println("Illegal number '" + loopTimes + "' as argument to '" + s + "'!");
+ return;
+ }
+ } else {
+ System.out.println("Argument '" + s + "' requires a numeric argument following it!");
+ return;
+ }
+ break;
+ case "movement":
+ case "move":
+ case "mov":
+ case "m":
+ movement = true;
+ break;
+ case "delays":
+ case "delay":
+ case "del":
+ case "d":
+ if(!argq.isEmpty()) {
+ String delaysString = argq.pollFirst();
+ String[] delaysSplit = delaysString.split(",");
+ delays = new float[delaysSplit.length];
+ for(int i=0; i= 0) System.out.println("Loading " + dmiFile);
+ DMI toImportTo = doDMILoad(dmiFile);
+ if(VERBOSITY >= 0) toImportTo.printInfo();
+ IconState is = IconState.importFromPNG(toImportTo, new FileInputStream(pngFile), stateName, delays, rewind, loop, hotspot, movement);
+
+ //image insertion
+ if(replaceDir != null || replaceFrame != null) {
+
+ IconState targetIs = toImportTo.getIconState(stateName);
+ if(targetIs == null) {
+ System.out.println("'direction' or 'frame' specified and no icon state '" + stateName + "' found, aborting!");
+ return;
+ }
+ if(is.images.length == 0) {
+ System.out.println("'direction' or 'frame' specified and imported is empty, aborting!");
+ return;
+ }
+
+ if(!noDup) targetIs = targetIs.clone();
+
+ int dirToReplace, frameToReplace;
+ if(replaceDir != null && replaceFrame != null) {
+ frameToReplace = parseFrame(replaceFrame, targetIs);
+ dirToReplace = parseDir(replaceDir, targetIs);
+ targetIs.insertImage(dirToReplace, frameToReplace, is.images[0]);
+ }
+ else if(replaceDir != null) {
+ dirToReplace = parseDir(replaceDir, targetIs);
+ targetIs.insertDir(dirToReplace, is.images);
+ }
+ else if(replaceFrame != null) {
+ frameToReplace = parseFrame(replaceFrame, targetIs);
+ targetIs.insertFrame(frameToReplace, is.images);
+ }
+
+ if(!noDup) toImportTo.addIconState(null, targetIs);
+ }
+ else {
+ if(noDup) {
+ if(!toImportTo.setIconState(is)) {
+ toImportTo.addIconState(null, is);
+ }
+ } else {
+ toImportTo.addIconState(null, is);
+ }
+ }
+
+ if(VERBOSITY >= 0) toImportTo.printInfo();
+
+ if(VERBOSITY >= 0) System.out.println("Saving " + dmiFile);
+ toImportTo.writeDMI(new FileOutputStream(dmiFile));
+ break;
+ }
+ case "verify": {
+ if(argq.size() < 1) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String vF = argq.pollFirst();
+ if(VERBOSITY >= 0) System.out.println("Loading " + vF);
+ DMI v = doDMILoad(vF);
+ if(VERBOSITY >= 0) v.printInfo();
+ break;
+ }
+ case "info": {
+ if(argq.size() < 1) {
+ System.out.println("Insufficient arguments for command!");
+ System.out.println(helpStr);
+ return;
+ }
+ String infoFile = argq.pollFirst();
+ if(VERBOSITY >= 0) System.out.println("Loading " + infoFile);
+ DMI info = doDMILoad(infoFile);
+ info.printInfo();
+ info.printStateList();
+ break;
+ }
+ case "version":
+ System.out.println(VERSION);
+ return;
+ default:
+ System.out.println("Command '" + op + "' not found!");
+ case "help":
+ System.out.println(helpStr);
+ break;
+ }
+ }
+
+ static int parseDir(String s, IconState is) {
+ try {
+ int i = Integer.parseInt(s);
+ if(0 <= i && i < is.dirs) {
+ return i;
+ } else {
+ System.out.println("Direction not in valid range [0, "+(is.dirs-1)+"]!");
+ return -1;
+ }
+ } catch(NumberFormatException nfe) {
+ for(int q=0; q