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