diff --git a/code/game/machinery/bots/farmbot.dm b/code/game/machinery/bots/farmbot.dm new file mode 100644 index 0000000000..ef58235157 --- /dev/null +++ b/code/game/machinery/bots/farmbot.dm @@ -0,0 +1,590 @@ +//Farmbots by GauHelldragon - 12/30/2012 +// A new type of buildable aiBot that helps out in hydroponics + +// Made by using a robot arm on a water tank and then adding: +// A plant analyzer, a bucket, a mini-hoe and then a proximity sensor (in that order) + +// Will water, weed and fertilize plants that need it +// When emagged, it will "water", "weed" and "fertilize" humans instead +// Holds up to 10 fertilizers (only the type dispensed by the machines, not chemistry bottles) +// It will fill up it's water tank at a sink when low. + +// The behavior panel can be unlocked with hydroponics access and be modified to disable certain behaviors +// By default, it will ignore weeds and mushrooms, but can be set to tend to these types of plants as well. + + +#define FARMBOT_MODE_WATER 1 +#define FARMBOT_MODE_FERTILIZE 2 +#define FARMBOT_MODE_WEED 3 +#define FARMBOT_MODE_REFILL 4 +#define FARMBOT_MODE_WAITING 5 + +#define FARMBOT_ANIMATION_TIME 25 //How long it takes to use one of the action animations +#define FARMBOT_EMAG_DELAY 60 //How long of a delay after doing one of the emagged attack actions +#define FARMBOT_ACTION_DELAY 35 //How long of a delay after doing one of the normal actions + +/obj/machinery/bot/farmbot + name = "Farmbot" + desc = "The botanist's best friend." + icon = 'aibots.dmi' + icon_state = "farmbot0" + layer = 5.0 + density = 1 + anchored = 0 + health = 50 + maxhealth = 50 + req_access =list(access_hydroponics) + + var/Max_Fertilizers = 10 + + var/setting_water = 1 + var/setting_refill = 1 + var/setting_fertilize = 1 + var/setting_weed = 1 + var/setting_ignoreWeeds = 1 + var/setting_ignoreMushrooms = 1 + + var/atom/target //Current target, can be a human, a hydroponics tray, or a sink + var/mode //Which mode is being used, 0 means it is looking for work + + var/obj/structure/reagent_dispensers/watertank/tank // the water tank that was used to make it, remains inside the bot. + + var/path[] = new() // used for pathing + var/frustration + +/obj/machinery/bot/farmbot/New() + ..() + src.icon_state = "farmbot[src.on]" + spawn (4) + src.botcard = new /obj/item/weapon/card/id(src) + src.botcard.access = req_access + + if ( !tank ) //Should be set as part of making it... but lets check anyway + tank = locate(/obj/structure/reagent_dispensers/watertank/) in contents + if ( !tank ) //An admin must have spawned the farmbot! Better give it a tank. + tank = new /obj/structure/reagent_dispensers/watertank(src) + +/obj/machinery/bot/farmbot/Bump(M as mob|obj) //Leave no door unopened! + spawn(0) + if ((istype(M, /obj/machinery/door)) && (!isnull(src.botcard))) + var/obj/machinery/door/D = M + if (!istype(D, /obj/machinery/door/firedoor) && D.check_access(src.botcard)) + D.open() + src.frustration = 0 + return + return + +/obj/machinery/bot/farmbot/turn_on() + . = ..() + src.icon_state = "farmbot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/farmbot/turn_off() + ..() + src.path = new() + src.icon_state = "farmbot[src.on]" + src.updateUsrDialog() + +/obj/machinery/bot/farmbot/attack_paw(mob/user as mob) + return attack_hand(user) + + +/obj/machinery/bot/farmbot/proc/get_total_ferts() + var total_fert = 0 + for (var/obj/item/nutrient/fert in contents) + total_fert++ + return total_fert + +/obj/machinery/bot/farmbot/attack_hand(mob/user as mob) + . = ..() + if (.) + return + var/dat + dat += "Automatic Hyrdoponic Assisting Unit v1.0

" + dat += "Status: [src.on ? "On" : "Off"]
" + + dat += "Water Tank: " + if ( tank ) + dat += "\[[tank.reagents.total_volume]/[tank.reagents.maximum_volume]\]" + else + dat += "Error: Water Tank not Found" + + dat += "
Fertilizer Storage: \[[get_total_ferts()]/[Max_Fertilizers]\]" + + dat += "
Behaviour controls are [src.locked ? "locked" : "unlocked"]
" + if(!src.locked) + dat += "Watering Controls:
" + dat += " Water Plants : [src.setting_water ? "Yes" : "No"]
" + dat += " Refill Watertank : [src.setting_refill ? "Yes" : "No"]
" + dat += "
Fertilizer Controls:
" + dat += " Fertilize Plants : [src.setting_fertilize ? "Yes" : "No"]
" + dat += "
Weeding Controls:
" + dat += " Weed Plants : [src.setting_weed ? "Yes" : "No"]
" + dat += "
Ignore Weeds : [src.setting_ignoreWeeds ? "Yes" : "No"]
" + dat += "Ignore Mushrooms : [src.setting_ignoreMushrooms ? "Yes" : "No"]
" + dat += "
" + + user << browse("Farmbot v1.0 controls[dat]", "window=autofarm") + onclose(user, "autofarm") + return + +/obj/machinery/bot/farmbot/Topic(href, href_list) + if(..()) + return + usr.machine = src + src.add_fingerprint(usr) + if ((href_list["power"]) && (src.allowed(usr))) + if (src.on) + turn_off() + else + turn_on() + + else if((href_list["water"]) && (!src.locked)) + setting_water = !setting_water + else if((href_list["refill"]) && (!src.locked)) + setting_refill = !setting_refill + else if((href_list["fertilize"]) && (!src.locked)) + setting_fertilize = !setting_fertilize + else if((href_list["weed"]) && (!src.locked)) + setting_weed = !setting_weed + else if((href_list["ignoreWeed"]) && (!src.locked)) + setting_ignoreWeeds = !setting_ignoreWeeds + else if((href_list["ignoreMush"]) && (!src.locked)) + setting_ignoreMushrooms = !setting_ignoreMushrooms + else if (href_list["eject"] ) + flick("farmbot_hatch",src) + for (var/obj/item/nutrient/fert in contents) + fert.loc = get_turf(src) + + src.updateUsrDialog() + return + +/obj/machinery/bot/farmbot/attackby(obj/item/weapon/W as obj, mob/user as mob) + if (istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) + if (src.allowed(user)) + src.locked = !src.locked + user << "Controls are now [src.locked ? "locked." : "unlocked."]" + src.updateUsrDialog() + else + user << "\red Access denied." + + else if (istype(W, /obj/item/nutrient)) + if ( get_total_ferts() >= Max_Fertilizers ) + user << "The fertilizer storage is full!" + return + user.drop_item() + W.loc = src + user << "You insert [W]." + flick("farmbot_hatch",src) + src.updateUsrDialog() + return + + else + ..() + +/obj/machinery/bot/farmbot/Emag(mob/user as mob) + ..() + if(user) user << "\red You short out [src]'s plant identifier circuits." + spawn(0) + for(var/mob/O in hearers(src, null)) + O.show_message("\red [src] buzzes oddly!", 1) + flick("farmbot_broke", src) + src.emagged = 1 + src.on = 1 + src.icon_state = "farmbot[src.on]" + target = null + mode = FARMBOT_MODE_WAITING //Give the emagger a chance to get away! 15 seconds should be good. + spawn(150) + mode = 0 + +/obj/machinery/bot/farmbot/explode() + src.on = 0 + visible_message("\red [src] blows apart!", 1) + var/turf/Tsec = get_turf(src) + + new /obj/item/weapon/minihoe(Tsec) + new /obj/item/weapon/reagent_containers/glass/bucket(Tsec) + new /obj/item/device/assembly/prox_sensor(Tsec) + new /obj/item/device/analyzer/plant_analyzer(Tsec) + + if ( tank ) + tank.loc = Tsec + + for ( var/obj/item/nutrient/fert in contents ) + if ( prob(50) ) + fert.loc = Tsec + + if (prob(50)) + new /obj/item/robot_parts/l_arm(Tsec) + + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + del(src) + return + +/obj/machinery/bot/farmbot/process() + set background = 1 + + if(!src.on) + return + + if ( emagged && prob(1) ) + flick("farmbot_broke", src) + + if ( mode == FARMBOT_MODE_WAITING ) + return + + if ( !mode || !target || !(target in view(7,src)) ) //Don't bother chasing down targets out of view + + mode = 0 + target = null + if ( !find_target() ) + // Couldn't find a target, wait a while before trying again. + mode = FARMBOT_MODE_WAITING + spawn(100) + mode = 0 + return + + if ( mode && target ) + if ( get_dist(target,src) <= 1 || ( emagged && mode == FARMBOT_MODE_FERTILIZE ) ) + // If we are in emagged fertilize mode, we throw the fertilizer, so distance doesn't matter + frustration = 0 + use_farmbot_item() + else + move_to_target() + return + +/obj/machinery/bot/farmbot/proc/use_farmbot_item() + if ( !target ) + mode = 0 + return 0 + + if ( emagged && !ismob(target) ) // Humans are plants! + mode = 0 + target = null + return 0 + + if ( !emagged && !istype(target,/obj/machinery/hydroponics) && !istype(target,/obj/structure/sink) ) // Humans are not plants! + mode = 0 + target = null + return 0 + + if ( mode == FARMBOT_MODE_FERTILIZE ) + //Find which fertilizer to use + var/obj/item/nutrient/fert + for ( var/obj/item/nutrient/nut in contents ) + fert = nut + break + if ( !fert ) + target = null + mode = 0 + return + fertilize(fert) + + if ( mode == FARMBOT_MODE_WEED ) + weed() + + if ( mode == FARMBOT_MODE_WATER ) + water() + + if ( mode == FARMBOT_MODE_REFILL ) + refill() + + + + +/obj/machinery/bot/farmbot/proc/find_target() + if ( emagged ) //Find a human and help them! + for ( var/mob/living/carbon/human/human in view(7,src) ) + if (human.stat == 2) + continue + + var list/options = list(FARMBOT_MODE_WEED) + if ( get_total_ferts() ) + options.Add(FARMBOT_MODE_FERTILIZE) + if ( tank && tank.reagents.total_volume >= 1 ) + options.Add(FARMBOT_MODE_WATER) + mode = pick(options) + target = human + return mode + return 0 + else + if ( setting_refill && tank && tank.reagents.total_volume < 100 ) + for ( var/obj/structure/sink/source in view(7,src) ) + target = source + mode = FARMBOT_MODE_REFILL + return 1 + for ( var/obj/machinery/hydroponics/tray in view(7,src) ) + var newMode = GetNeededMode(tray) + if ( newMode ) + mode = newMode + target = tray + return 1 + return 0 + +/obj/machinery/bot/farmbot/proc/GetNeededMode(obj/machinery/hydroponics/tray) + if ( !tray.planted || tray.dead ) + return 0 + if ( tray.myseed.plant_type == 1 && setting_ignoreWeeds ) + return 0 + if ( tray.myseed.plant_type == 2 && setting_ignoreMushrooms ) + return 0 + + if ( setting_water && tray.waterlevel <= 10 && tank && tank.reagents.total_volume >= 1 ) + return FARMBOT_MODE_WATER + + if ( setting_weed && tray.weedlevel >= 5 ) + return FARMBOT_MODE_WEED + + if ( setting_fertilize && tray.nutrilevel <= 2 && get_total_ferts() ) + return FARMBOT_MODE_FERTILIZE + + return 0 + +/obj/machinery/bot/farmbot/proc/move_to_target() + //Mostly copied from medibot code. + + if(src.frustration > 8) + target = null + mode = 0 + frustration = 0 + src.path = new() + if(src.target && (src.path.len) && (get_dist(src.target,src.path[src.path.len]) > 2)) + src.path = new() + if(src.target && src.path.len == 0 && (get_dist(src,src.target) > 1)) + spawn(0) + var/turf/dest = get_step_towards(target,src) //Can't pathfind to a tray, as it is dense, so pathfind to the spot next to the tray + + src.path = AStar(src.loc, dest, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30,id=botcard) + src.path = reverselist(src.path) + if(src.path.len == 0) + for ( var/turf/spot in orange(1,target) ) //The closest one is unpathable, try the other spots + if ( spot == dest ) //We already tried this spot + continue + if ( spot.density ) + continue + src.path = AStar(src.loc, spot, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30,id=botcard) + src.path = reverselist(src.path) + if ( src.path.len > 0 ) + break + + if ( src.path.len == 0 ) + target = null + mode = 0 + return + + if(src.path.len > 0 && src.target) + step_to(src, src.path[1]) + src.path -= src.path[1] + spawn(3) + if(src.path.len) + step_to(src, src.path[1]) + src.path -= src.path[1] + + if(src.path.len > 8 && src.target) + src.frustration++ + + +/obj/machinery/bot/farmbot/proc/fertilize(obj/item/nutrient/fert) + if ( !fert ) + target = null + mode = 0 + return 0 + + if ( emagged ) // Warning, hungry humans detected: throw fertilizer at them + spawn(0) + fert.loc = src.loc + fert.throw_at(target, 16, 3) + src.visible_message("\red [src] launches [fert.name] at [target.name]!") + flick("farmbot_broke", src) + spawn (FARMBOT_EMAG_DELAY) + mode = 0 + target = null + return 1 + + else // feed them plants~ + var /obj/machinery/hydroponics/tray = target + tray.nutrilevel = 10 + tray.yieldmod = fert.yieldmod + tray.mutmod = fert.mutmod + del fert + tray.updateicon() + icon_state = "farmbot_fertile" + mode = FARMBOT_MODE_WAITING + + spawn (FARMBOT_ACTION_DELAY) + mode = 0 + target = null + spawn (FARMBOT_ANIMATION_TIME) + icon_state = "farmbot[src.on]" + return 1 + +/obj/machinery/bot/farmbot/proc/weed() + icon_state = "farmbot_hoe" + spawn(FARMBOT_ANIMATION_TIME) + icon_state = "farmbot[src.on]" + + if ( emagged ) // Warning, humans infested with weeds! + mode = FARMBOT_MODE_WAITING + spawn(FARMBOT_EMAG_DELAY) + mode = 0 + + if ( prob(50) ) // better luck next time little guy + src.visible_message("\red [src] swings wildly at [target] with a minihoe, missing completely!") + + else // yayyy take that weeds~ + var/attackVerb = pick("slashed", "sliced", "cut", "clawed") + var /mob/living/carbon/human/human = target + + src.visible_message("\red [src] [attackVerb] [human]!") + var/damage = 5 + var/dam_zone = pick("chest", "l_hand", "r_hand", "l_leg", "r_leg") + var/datum/organ/external/affecting = human.get_organ(ran_zone(dam_zone)) + var/armor = human.run_armor_check(affecting, "melee") + human.apply_damage(damage,BRUTE,affecting,armor) + + else // warning, plants infested with weeds! + mode = FARMBOT_MODE_WAITING + spawn(FARMBOT_ACTION_DELAY) + mode = 0 + + var /obj/machinery/hydroponics/tray = target + tray.weedlevel = 0 + tray.updateicon() + +/obj/machinery/bot/farmbot/proc/water() + if ( !tank || tank.reagents.total_volume < 1 ) + mode = 0 + target = null + return 0 + + icon_state = "farmbot_water" + spawn(FARMBOT_ANIMATION_TIME) + icon_state = "farmbot[src.on]" + + if ( emagged ) // warning, humans are thirsty! + var splashAmount = min(70,tank.reagents.total_volume) + src.visible_message("\red [src] splashes [target] with a bucket of water!") + playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) + if ( prob(50) ) + tank.reagents.reaction(target, TOUCH) //splash the human! + else + tank.reagents.reaction(target.loc, TOUCH) //splash the human's roots! + spawn(5) + tank.reagents.remove_any(splashAmount) + + mode = FARMBOT_MODE_WAITING + spawn(FARMBOT_EMAG_DELAY) + mode = 0 + else + var /obj/machinery/hydroponics/tray = target + var/b_amount = tank.reagents.get_reagent_amount("water") + if(b_amount > 0 && tray.waterlevel < 100) + if(b_amount + tray.waterlevel > 100) + b_amount = 100 - tray.waterlevel + tank.reagents.remove_reagent("water", b_amount) + tray.waterlevel += b_amount + playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) + + // Toxicity dilutation code. The more water you put in, the lesser the toxin concentration. + tray.toxic -= round(b_amount/4) + if (tray.toxic < 0 ) // Make sure it won't go overboard + tray.toxic = 0 + + tray.updateicon() + mode = FARMBOT_MODE_WAITING + spawn(FARMBOT_ACTION_DELAY) + mode = 0 + +/obj/machinery/bot/farmbot/proc/refill() + if ( !tank || !tank.reagents.total_volume > 600 || !istype(target,/obj/structure/sink) ) + mode = 0 + target = null + return + + mode = FARMBOT_MODE_WAITING + playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) + src.visible_message("\blue [src] starts filling it's tank from [target].") + spawn(300) + src.visible_message("\blue [src] finishes filling it's tank.") + src.mode = 0 + tank.reagents.add_reagent("water", tank.reagents.maximum_volume - tank.reagents.total_volume ) + playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) + + +/obj/item/weapon/farmbot_arm_assembly + name = "water tank/robot arm assembly" + desc = "A water tank with a robot arm permanently grafted to it." + icon = 'aibots.dmi' + icon_state = "water_arm" + var/build_step = 0 + var/created_name = "Farmbot" //To preserve the name if it's a unique farmbot I guess + w_class = 3.0 + + New() + ..() + spawn(4) // If an admin spawned it, it won't have a watertank it, so lets make one for em! + var tank = locate(/obj/structure/reagent_dispensers/watertank) in contents + if( !tank ) + new /obj/structure/reagent_dispensers/watertank(src) + + +/obj/structure/reagent_dispensers/watertank/attackby(var/obj/item/robot_parts/S, mob/user as mob) + + if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm))) + ..() + return + + //Making a farmbot! + + var/obj/item/weapon/farmbot_arm_assembly/A = new /obj/item/weapon/farmbot_arm_assembly + + A.loc = src.loc + A.layer = 20 + user << "You add the robot arm to the [src]" + src.loc = A //Place the water tank into the assembly, it will be needed for the finished bot + + del(S) + +/obj/item/weapon/farmbot_arm_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob) + ..() + if((istype(W, /obj/item/device/analyzer/plant_analyzer)) && (!src.build_step)) + src.build_step++ + user << "You add the plant analyzer to [src]!" + src.name = "farmbot assembly" + del(W) + + else if(( istype(W, /obj/item/weapon/reagent_containers/glass/bucket)) && (src.build_step == 1)) + src.build_step++ + user << "You add a bucket to [src]!" + src.name = "farmbot assembly with bucket" + del(W) + + else if(( istype(W, /obj/item/weapon/minihoe)) && (src.build_step == 2)) + src.build_step++ + user << "You add a minihoe to [src]!" + src.name = "farmbot assembly with bucket and minihoe" + del(W) + + else if((isprox(W)) && (src.build_step == 3)) + src.build_step++ + user << "You complete the Farmbot! Beep boop." + var/obj/machinery/bot/farmbot/S = new /obj/machinery/bot/farmbot + for ( var/obj/structure/reagent_dispensers/watertank/wTank in src.contents ) + wTank.loc = S + S.tank = wTank + S.loc = get_turf(src) + S.name = src.created_name + del(W) + del(src) + + else if(istype(W, /obj/item/weapon/pen)) + var/t = input(user, "Enter new robot name", src.name, src.created_name) as text + t = copytext(sanitize(t), 1, MAX_NAME_LEN) + if (!t) + return + if (!in_range(src, usr) && src.loc != usr) + return + + src.created_name = t diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index a511f3b214..edef1e910b 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -42,10 +42,7 @@ uneq_all() src.stat = 1 else if (src.cell.charge <= 100) - src.module_active = null - src.module_state_1 = null - src.module_state_2 = null - src.module_state_3 = null + uneq_all() src.sight_mode = 0 src.cell.use(1) else @@ -161,6 +158,10 @@ src.see_in_dark = 8 src.see_invisible = SEE_INVISIBLE_LEVEL_TWO + for(var/image/hud in client.images) //COPIED FROM the human handle_regular_hud_updates() proc + if(copytext(hud.icon_state,1,4) == "hud") //ugly, but icon comparison is worse, I believe + del(hud) + var/obj/item/borg/sight/hud/hud = (locate(/obj/item/borg/sight/hud) in src) if(hud && hud.hud) hud.hud.process_hud(src)